
Una exploración controlada del mecanismo de vinculación durante el page-in de dyld y de los chained fixups como oráculo de firma PAC, en el contexto de CVE-2026-20700.
En arm64e, cada puntero a función está autenticado por hardware. El objetivo aquí es demostrar que se puede dirigir al propio dyld — mediante un Mach-O manipulado artesanalmente — para que produzca punteros válidos para PAC en ranuras elegidas por el atacante, usando únicamente su propio mecanismo normal de fixup.
Probado en: iPhone 14 (iOS 18.5, arm64e).
__DATA.fixupPage64 — los valores malformados de page_start / next sacan a dyld de los límites de la página, demostrando la alcanzabilidad de la rama.dispatch_source_t y el bucle de eventos lo invoca de forma natural, sin que el código del PoC lo llame directamente.dyld-signing-oracle-poc/
├── Makefile ← orquesta el pipeline completo
├── src/
│ └── launcher.c ← lanzador para iOS (2 hilos + cierre de cadena de *dispatch*)
├── generators/
│ ├── gen_exports.py ← genera exports.c (N símbolos dummy)
│ ├── gen_client.py ← genera client.c (N importaciones, estrés en la compuerta de dyld)
│ └── gen_malformed_dylib.py ← genera libmalformed.dylib (Mach-O manipulado artesanalmente)
├── tools/
│ ├── scan_pointers.py ← clasifica las secciones de punteros del Mach-O como W/R
│ └── inspect_fixups.py ← analiza el encabezado de LC_DYLD_CHAINED_FIXUPS
└── blog/
├── it/
│ └── dyld-signing-oracle.md ← write-up completo in italiano
└── en/
└── dyld-signing-oracle.md ← write-up completo en inglés
xcrun --sdk iphoneos --show-sdk-path)# Compilación completa predeterminada (arm64, 99k símbolos)
make
# Compilación rápida para iterar
make SYMBOLS=10000
# Bloqueo determinista en fixupPage64 (prueba de alcanzabilidad de la rama)
make stress
# Escritura estable write-what-where dentro de la misma imagen
make exploit
# dyld escribe un puntero válido para PAC → dispatch lo invoca de forma natural
make chain_close
# Verificar el diseño del blob de chained fixups generado
make verify
# Analizar el encabezado LC_DYLD_CHAINED_FIXUPS de libmalformed.dylib
make inspect
# Escanear secciones de punteros (GOT/no perezosas) en los binarios compilados
make scan
# Firma ad-hoc (Sideloadly)
make chain_close
# arrastra PoCApp.ipa a Sideloadly
# Con certificado real
make resign IDENTITY="iPhone Developer: ..." PROFILE=embedded.mobileprovision
# Supervisar registros
idevicesyslog | grep "POC"
Orden de carga en tiempo de ejecución dentro de PoCApp:
libexports.dylib — se carga primero (RTLD_GLOBAL), proporciona write_target_value y attacker_hook a cualquier dlopen posterior.libclient.dylib — lo carga el Hilo B (pila de 128KB); ~99k objetivos de bind someten a estrés la compuerta de vinculación por page-in de dyld.libmalformed.dylib — lo carga el Hilo A; la cadena de chained fixups manipulada artesanalmente hace que dyld resuelva y escriba _write_target_value (y opcionalmente _attacker_hook) en __DATA+0x10 / +0x20.chain_close, el hilo principal registra __DATA+0x20 como manejador de temporizador — el bucle de eventos lo invoca 1 segundo después sin que el código del PoC lo llame directamente.Artículo técnico completo en italiano e inglés. Cubre: mecánica del hardware PAC, dyld como productor de punteros, codificación de chained fixups desde cero, mecánica de la compuerta de vinculación por page-in, dificultades de ingeniería del Mach-O (sizeofcmds, número de secciones, stride), diseño de datos validado con canarios, la compuerta de los 99k símbolos, prueba de bloqueo y alcanzabilidad, primitiva estable dentro de la misma imagen, concepto de oráculo de firma en arm64e y cierre de la cadena con temporizador de dispatch.
| Archivo | Generado por |
|---|
exports.c | generators/gen_exports.py + símbolos centinela del Makefile |
client.c | generators/gen_client.py |
libmalformed.dylib | generators/gen_malformed_dylib.py |
libexports.dylib | clang desde exports.c |
libclient.dylib | clang desde client.c |
PoCApp | clang desde src/launcher.c |
PoCApp.ipa | paso package del Makefile |
| Variable | Valor por defecto | Descripción |
|---|
SYMBOLS | 99000 | Número de objetivos de bind en libclient (< 100k para la compuerta anterior a 26.3, < 64k para 26.3+) |
STACK_KB | 128 | Tamaño de pila del hilo de trabajo en KB |
BURN_KB | 0 | KB de pila a consumir antes de dlopen (0 = automático) |
MARGIN_KB | 24 | Margen de consumo automático de pila |
ARM64E | 0 | Usar el formato DYLD_CHAINED_PTR_ARM64E_USERLAND24 |
MALFORM_PAGEIN | 0 | Habilitar cadena de page-in malformada |
MALFORM_TARGET_OFFSET | — | Desplazamiento objetivo dentro de __DATA (p. ej. 0x10) |
CHAIN_CLOSE | 0 | Segunda ranura → _attacker_hook, habilitar la demo de dispatch |
dispatch_source_t