
Eine kontrollierte Untersuchung der Page-in-Linking- und Chained-Fixup-Mechanik von dyld als PAC-Signing-Orakel im Kontext von CVE-2026-20700.
Auf arm64e ist jeder Funktionszeiger hardware-authentifiziert. Das Ziel hier ist zu zeigen, dass dyld selbst — durch ein handgefertigtes Mach-O — dazu gebracht werden kann, PAC-gültige Zeiger in vom Angreifer gewählte Slots zu erzeugen, und zwar nur mit seinem eigenen normalen Fixup-Mechanismus.
Getestet auf: iPhone 14 (iOS 18.5, arm64e).
__DATA.fixupPage64 — fehlerhafte page_start-/next-Werte treiben dyld über die Seitengrenze hinaus, was die Branch-Erreichbarkeit beweist.dispatch_source_t-Timer-Handler registriert und auf natürliche Weise über die Event-Loop aufgerufen, ohne direkten Aufruf aus dem PoC-Code.dyld-signing-oracle-poc/
├── Makefile ← orchestrates the full pipeline
├── src/
│ └── launcher.c ← iOS launcher (2 threads + dispatch chain-close)
├── generators/
│ ├── gen_exports.py ← generates exports.c (N dummy symbols)
│ ├── gen_client.py ← generates client.c (N imports, dyld gate stress)
│ └── gen_malformed_dylib.py ← generates libmalformed.dylib (hand-crafted Mach-O)
├── tools/
│ ├── scan_pointers.py ← classifies Mach-O pointer sections as W/R
│ └── inspect_fixups.py ← parses LC_DYLD_CHAINED_FIXUPS header
└── blog/
├── it/
│ └── dyld-signing-oracle.md ← write-up completo in italiano
└── en/
└── dyld-signing-oracle.md ← full write-up in English
xcrun --sdk iphoneos --show-sdk-path)# Default full build (arm64, 99k symbols)
make
# Fast build for iteration
make SYMBOLS=10000
# Deterministic crash in fixupPage64 (branch reachability proof)
make stress
# Stable intra-image write-what-where
make exploit
# dyld writes PAC-valid pointer → dispatch calls it naturally
make chain_close
# Verify generated chained-fixup blob layout
make verify
# Parse LC_DYLD_CHAINED_FIXUPS header of libmalformed.dylib
make inspect
# Scan pointer sections (GOT/non-lazy) across compiled binaries
make scan
# Ad-hoc signed (Sideloadly)
make chain_close
# drag PoCApp.ipa into Sideloadly
# Real certificate
make resign IDENTITY="iPhone Developer: ..." PROFILE=embedded.mobileprovision
# Monitor logs
idevicesyslog | grep "POC"
Ladereihenfolge zur Laufzeit innerhalb von PoCApp:
libexports.dylib — wird zuerst geladen (RTLD_GLOBAL) und stellt write_target_value und attacker_hook für jedes spätere dlopen bereit.libclient.dylib — wird von Thread B (128-KB-Stack) geladen; ~99k Bind-Ziele belasten das Page-in-Linking-Gate von dyld.libmalformed.dylib — wird von Thread A geladen; eine handgefertigte Chained-Fixup-Kette veranlasst dyld, _write_target_value (und optional _attacker_hook) in __DATA+0x10 / +0x20 aufzulösen und zu schreiben.chain_close-Modus registriert main als -Timer-Handler — die Event-Loop ruft ihn 1 Sekunde später auf, ohne direkten Aufruf aus dem PoC-Code.Vollständiger technischer Artikel auf Italienisch und Englisch. Behandelt: PAC-Hardware-Mechanik, dyld als Zeiger-Produzent, Chained-Fixup-Kodierung von Grund auf, Mechanik des Page-in-Linking-Gates, Mach-O-Engineering-Fallstricke (sizeofcmds, Sektionsanzahl, Stride), Canary-validiertes Datenlayout, das 99k-Symbol-Gate, Absturzbeweis der Erreichbarkeit, stabile Intra-Image-Primitive, das arm64e-Signing-Orakel-Konzept, Dispatch-Timer-Chain-Close.
| Datei | Erzeugt durch |
|---|
exports.c | generators/gen_exports.py + Makefile-Sentinel-Symbole |
client.c | generators/gen_client.py |
libmalformed.dylib | generators/gen_malformed_dylib.py |
libexports.dylib | clang aus exports.c |
libclient.dylib | clang aus client.c |
PoCApp | clang aus src/launcher.c |
PoCApp.ipa | Makefile-package-Schritt |
| Variable | Standard | Beschreibung |
|---|
SYMBOLS | 99000 | Anzahl der Bind-Ziele in libclient (< 100k für Pre-26.3-Gate, < 64k für 26.3+) |
STACK_KB | 128 | Stackgröße des Worker-Threads in KB |
BURN_KB | 0 | Zu verbrauchende Stack-KB vor dlopen (0 = automatisch) |
MARGIN_KB | 24 | Marge für den automatischen Burn |
ARM64E | 0 | DYLD_CHAINED_PTR_ARM64E_USERLAND24-Format verwenden |
MALFORM_PAGEIN | 0 | Fehlerhafte Page-in-Kette aktivieren |
MALFORM_TARGET_OFFSET | — | Ziel-Offset innerhalb __DATA (z. B. 0x10) |
CHAIN_CLOSE | 0 | Zweiter Slot → _attacker_hook, Dispatch-Demo aktivieren |
__DATA+0x20dispatch_source_t