PoC 演示了通过 arm64e 上手工构造的 Mach-O 链式修复,将 dyld 用作 PAC 签名预言机,从而在 iOS 上实现受控的 PAC 有效指针写入与分发回调执行。
对 dyld 的 page-in 链接和 chained fixup 机制进行的一次受控探索,将其作为 PAC 签名预言机,背景为 CVE-2026-20700。
在 arm64e 上,每个函数指针都经过硬件认证。这里的目标是展示:通过手工构造的 Mach-O,dyld 本身可以被引导——仅利用其自身的常规 fixup 机制——向攻击者选定的槽位写出 PAC 有效的指针。
已在以下设备上测试:iPhone 14(iOS 18.5,arm64e)。
__DATA 中选定的槽位写入 PAC 有效的函数指针。fixupPage64 中确定性崩溃 — 畸形的 page_start / next 值使 dyld 越出页边界,证明分支可达性。dispatch_source_t 定时器处理函数,并通过事件循环自然调用,PoC 代码没有直接调用它。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
| 文件 | 生成方式 |
|---|---|
exports.c | generators/gen_exports.py + Makefile 哨兵符号 |
client.c | generators/gen_client.py |
libmalformed.dylib | generators/gen_malformed_dylib.py |
libexports.dylib | 由 exports.c 通过 clang 编译 |
libclient.dylib | 由 client.c 通过 clang 编译 |
PoCApp | 由 src/launcher.c 通过 clang 编译 |
PoCApp.ipa | Makefile package 步骤 |
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
| 变量 | 默认值 | 描述 |
|---|---|---|
SYMBOLS | 99000 | libclient 中的绑定目标数量(26.3 之前门槛 < 100k,26.3 及之后 < 64k) |
STACK_KB | 128 | 工作线程栈大小(KB) |
BURN_KB | 0 | dlopen 前要消耗的栈大小(KB);(0 = 自动) |
MARGIN_KB | 24 | 自动消耗的余量(KB) |
ARM64E | 0 | 使用 DYLD_CHAINED_PTR_ARM64E_USERLAND24 格式 |
MALFORM_PAGEIN | 0 | 启用畸形的 page-in 链 |
MALFORM_TARGET_OFFSET | — | __DATA 内的目标偏移(例如 0x10) |
CHAIN_CLOSE | 0 | 第二个槽 → _attacker_hook,启用 dispatch 演示 |
# 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"
PoCApp 内部的运行时加载顺序:
libexports.dylib — 最先加载(RTLD_GLOBAL),为后续任何 dlopen 提供 write_target_value 和 attacker_hook。libclient.dylib — 由线程 B 加载(128KB 栈);约 99k 个绑定目标压力测试 dyld 的 page-in 链接门槛。libmalformed.dylib — 由线程 A 加载;手工构造的 chained-fixup 链使 dyld 解析并将 _write_target_value(以及可选的 _attacker_hook)写入 __DATA+0x10 / +0x20。chain_close 模式下,主线程将 __DATA+0x20 注册为 dispatch_source_t 定时器处理函数——事件循环在 1 秒后调用它,PoC 代码没有直接调用。完整技术文章提供意大利语和英语版本。涵盖:PAC 硬件机制、dyld 作为指针生成器、从零开始讲解 chained-fixup 编码、page-in 链接门槛机制、Mach-O 工程陷阱(sizeofcmds、节数量、stride)、canary 验证的数据布局、99k 符号门槛、可达性崩溃证明、稳定的镜像内原语、arm64e 签名预言机概念、dispatch 定时器 chain-close。