## https://sploitus.com/exploit?id=19E5C0C2-0ADB-5CE5-9AD5-4D4B19A95247
# 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
```sh
# Default full build (arm64, 99k symbols)
make
# Fast build for iteration
make SYMBOLS=10000
```
---
## Presets
```sh
# 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
```sh
# 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
```sh
# 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.
- [๐ฎ๐น Italiano โ `blog/it/dyld-signing-oracle.md`](blog/it/dyld-signing-oracle.md)
- [๐ฌ๐ง English โ `blog/en/dyld-signing-oracle.md`](blog/en/dyld-signing-oracle.md)