Share
## 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)