Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dyld-signing-oracle-poc

A controlled exploration of dyld's page-in linking and chained fixup machinery as a PAC signing oracle, in the context of CVE-2026-20700.

On arm64e, every function pointer is hardware-authenticated. The goal here is to show that dyld itself can be directed — through a hand-crafted Mach-O — to produce PAC-valid pointers into attacker-chosen slots, using nothing but its own normal fixup mechanism.

Tested on: iPhone 14 (iOS 18.5, arm64e).


What this demonstrates

  1. Controlled chained-fixup bind — dyld accepts a hand-crafted Mach-O dylib and writes PAC-valid function pointers into chosen slots in its own __DATA.
  2. Deterministic crash in fixupPage64 — malformed page_start / next values drive dyld out of the page boundary, proving branch reachability.
  3. Dispatch event loop execution — the PAC-valid pointer written by dyld is registered as a dispatch_source_t timer handler and called naturally through the event loop, with no direct invocation from the PoC code.

Repository structure

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

Build outputs (not committed)

File Generated by
exports.c generators/gen_exports.py + Makefile sentinel symbols
client.c generators/gen_client.py
libmalformed.dylib generators/gen_malformed_dylib.py
libexports.dylib clang from exports.c
libclient.dylib clang from client.c
PoCApp clang from src/launcher.c
PoCApp.ipa Makefile package step

Requirements

  • macOS with Xcode command-line tools
  • iOS SDK (xcrun --sdk iphoneos --show-sdk-path)
  • Python 3
  • For device testing: Sideloadly or a developer certificate + provisioning profile

Build

# Default full build (arm64, 99k symbols)
make

# Fast build for iteration
make SYMBOLS=10000

Presets

# 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

Analysis tools

# 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

Tunables

Variable Default Description
SYMBOLS 99000 Bind target count in libclient (< 100k for pre-26.3 gate, < 64k for 26.3+)
STACK_KB 128 Worker thread stack size in KB
BURN_KB 0 Stack KB to consume before dlopen (0 = auto)
MARGIN_KB 24 Auto-burn margin
ARM64E 0 Use DYLD_CHAINED_PTR_ARM64E_USERLAND24 format
MALFORM_PAGEIN 0 Enable malformed page-in chain
MALFORM_TARGET_OFFSET — Target offset inside __DATA (e.g. 0x10)
CHAIN_CLOSE 0 Second slot → _attacker_hook, enable dispatch demo

Installing on device

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

How it works

Runtime load order inside PoCApp:

  1. libexports.dylib — loaded first (RTLD_GLOBAL), provides write_target_value and attacker_hook to any subsequent dlopen.
  2. libclient.dylib — loaded by Thread B (128KB stack); ~99k bind targets stress the dyld page-in linking gate.
  3. libmalformed.dylib — loaded by Thread A; hand-crafted chained-fixup chain makes dyld resolve and write _write_target_value (and optionally _attacker_hook) into __DATA+0x10 / +0x20.
  4. Thread A reads the written slots, validates canaries, calls the function pointer dyld wrote.
  5. In chain_close mode, main registers __DATA+0x20 as a dispatch_source_t timer handler — the event loop calls it 1 second later with no direct invocation from the PoC code.

Blog

Full technical write-up in Italian and English. Covers: PAC hardware mechanics, dyld as a pointer producer, chained-fixup encoding from scratch, page-in linking gate mechanics, Mach-O engineering pitfalls (sizeofcmds, section count, stride), canary-validated data layout, the 99k symbol gate, crash proof of reachability, stable intra-image primitive, arm64e signing oracle concept, dispatch timer chain-close.

About

No description, website, or topics provided.

Resources

Stars

14 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages