掌控他人进程的底层逻辑:iOS 越狱 Tweak 运行时注入、脱壳重签与 6 处静默陷阱实战复盘
在 iOS 安全攻防与系统底层研究中,改变一个第三方 App 的既定运行轨迹往往是极具挑战的课题。表面上看,将一款第三方应用(以经典轻量应用「骰子」为例)底部碍眼的广告横幅拦截、替换为自定义文案并在点击后优雅塌陷,只需要不到两百行的 Objective-C 代码;然而在现代苹果防御体系下,这一动作背后却牵引出两条底层哲学截然对立的交付管线:真机环境下的运行时动态注入,以及无越狱沙盒环境下的 Mach-O 内存脱壳、LC_LOAD_DYLIB 植入与重签名链条。
更关键的是,在这类直面 Mach-O 二进制、内核安全模块(AMFI)以及动态链接器(dyld)的深度工程中,最致命的敌人从不是显式的 Crash 报错,而是“工具正常退出、产物表面完整、但目标代码从未被执行”的静默假象。本文基于第一视角真实实战记录,完整拆解这两条交付链路的底层机制,并深度复盘 6 处极具隐蔽性的工程暗坑。
0. 核心全景:两条交付路径的底层分野
在不改变原 App 源码的前提下改造其行为,技术手段的选择完全取决于宿主环境的安全边界。我在本次攻坚中实现了同一份逻辑在两种环境下的全链路打通:
flowchart TD
Source["同一份 Tweak 动态库源码 (diceadbanner.m)"]
subgraph PathA ["路径 A:越狱真机 (Dopamine + ElleKit)"]
CompileA["clang 编译为通用 arm64/arm64e dylib"]
SignA["ldid -S 做 ad-hoc 签名"]
Deb["打包为标准 .deb (带 Filter.plist)"]
DeployA["dpkg 安装至 /var/jb/.../TweakInject"]
RuntimeA["应用启动时被替换的 dyld (systemhook) 动态加载"]
RunA["App 包完全保持原版 FairPlay 密文,0 改动"]
end
subgraph PathB ["路径 B:无越狱沙盒 / 虚拟设备 (vphone guest)"]
DumpB["Frida 内存转储 FairPlay 解密段 (4074 字节)"]
PatchB["二进制打补丁并置 cryptid = 0"]
InjectB["修改 Mach-O Header:追加 LC_LOAD_DYLIB"]
ResignB["对主可执行文件与所有 Frameworks 重签名 (ldid 格式)"]
RepackB["重打包为 IPA 并通过通信通道安装"]
RunB["修改后的 App 二进制直接由内核加载并运行"]
end
Source --> CompileA
CompileA --> SignA --> Deb --> DeployA --> RuntimeA --> RunA
Source --> DumpB
DumpB --> PatchB --> InjectB --> ResignB --> RepackB --> RunB
如上图所示,两条路线的本质差异在于 注入发生的时机与空间:
| 评估维度 | 真机越狱交付(ElleKit 动态注入) | 虚拟/未越狱交付(IPA 改包注入) |
|---|---|---|
| 代码注入时机 | 运行时(Runtime):App 进程启动阶段 | 构建时(Build-time):修改磁盘二进制 |
| FairPlay 密文解密 | ❌ 完全不需要(磁盘保持密文,内核加载时透明解密) | ✅ 必须解密(无法直接在密文上修改 Load Command) |
| 代码重签名 | ❌ 不需要(原 App 签名未动,Tweak 自身仅需 ad-hoc) | ✅ 必须全量重签(逐页哈希已破坏,不签则 AMFI 拒载) |
| 原 App 完整性 | 100% 原封不动(App Store 原版) | 二进制被修改(增加依赖库、重置加密标记) |
| 部署步骤与成本 | 1 步(推送 .deb 安装,卸载仅需 dpkg -r) |
5 步(脱壳 → 补丁 → 注入 → 重签 → 重打包) |
| 核心工程价值 | 极简、干净、无副作用,调试周期极短 | 脱离越狱限制,便于构建高度隔离的自动化实验台 |
1. 概念澄清:越狱工具链的四层物理拓扑
在深入代码之前,必须彻底理清 iOS 逆向生态中被滥用的一系列专有名词。被改造的 App 好比一栋封顶的现代大楼,我需要把一段定制代码塞入其中。从最底层的内存修改到最外层的安装分发,技术栈清晰地划分为四个层次:
flowchart TD
subgraph L4 ["第 4 层:分发与安装 (Distribution)"]
DEB[".deb 软件包"] --> PackageMgr["包管理器 (dpkg / Sileo / Zebra)"]
end
subgraph L3 ["第 3 层:注入执行器 (Injector)"]
HookLoader["ElleKit / Cydia Substrate / Substitute (越狱动态载入)"]
LoadCommand["Mach-O LC_LOAD_DYLIB (未越狱改包静态载入)"]
end
subgraph L2 ["第 2 层:代码实体 (Payload)"]
Dylib[".dylib 动态库文件 (Mach-O MH_DYLIB)"]
Tweak["Tweak = 寄生型 dylib + Filter.plist 白名单"]
Dylib --> Tweak
end
subgraph L1 ["第 1 层:行为篡改底层技术 (Mechanism)"]
Swizzle["ObjC Method Swizzling (修改 Class 方法分发表)"]
InlineHook["Inline Hook (改写函数首指令跳转,适用于 C/纯 Swift)"]
FishHook["Fishhook (重绑定 Mach-O 符号导入表 __la_symbol_ptr)"]
end
L4 --> L3
L3 --> L2
L2 --> L1
1.1 dylib 与 Tweak 的真实关系
- dylib(Dynamic Library):苹果平台标准的动态共享库(Mach-O 类型为
MH_DYLIB)。其特征是程序启动后由动态链接器dyld映射进内存,具备独立的install_name,并通过导出符号与其他模块交互。在一个典型的 iOS 进程中,往往同时映射着多达数百个 dylib(例如骰子进程实测挂载了 823 个动态库镜像,绝大多数由 dyld shared cache 预加载)。 - Tweak 的特化定义:在越狱语境中,Tweak 不是独立存在的 App,其本质是一个专门寄生于他人进程空间、旨在劫持并修改宿主逻辑的 dylib。它绝不会独立运行,必须配合一个同名的
Filter.plist过滤器文件,向注入器声明目标应用的 Bundle ID。
1.2 注入框架的演进代际
从 Cydia Substrate、Substitute 到 Dopamine 时代的 ElleKit,注入框架的核心使命始终聚焦于两点:
- 进程启动加载器:在目标进程刚拉起时,截获其生命周期,解析
TweakInject目录下的 plist 规则,若当前 Bundle ID 命中白名单,则调用dlopen将对应 dylib 映射入该进程地址空间; - Hook API 封装:提供跨架构、标准化的指令级 Hook 接口(如
MSHookMessageEx、MSHookFunction)。
[!NOTE]
兼容性红利:尽管三代框架内核更迭,但它们全部沿用了最初由 Saurik 确立的目录布局与Filter.plist结构。这解释了为何一段编写规范的 Tweak 产物能够零修改跨越不同越狱版本稳定加载。
1.3 为何本案放弃 Theos 转用裸 clang 编译?
Theos 是越狱开发中经典的 Makefile 框架与 Logos(%hook / %orig)语法糖集合。但在本案中,我选择直接调用 Xcode 原生工具链进行编译与打包:
- 目标极其聚焦:单源文件实现(不到 200 行),无需庞大的 Makefile 抽象;
- 底层清晰透明:手动调用
class_replaceMethod仅需十余行代码,摆脱 Logos 预处理层可更直观地审视编译输出与签名逻辑; - 消除外部依赖:仅需调用 macOS 宿主已有的
xcrun clang、ldid与dpkg-deb,便于构建极简的自动化流水线。
2. 深入机理:Dopamine 运行时加载与 Mach-O 静态注入
2.1 轨一:ElleKit 运行时九步加载链路
在装配 Dopamine(Rootless)的真机环境下,一个 Tweak 从磁盘文件到真正掌控 App 内存,严格遵循以下 9 步精密链路:
① 用户点击屏幕图标启动 App
↓
② XNU 内核创建进程并映射主可执行文件,将控制权移交定制版 dyld(gen/dyld)
↓
③ dyld 按照越狱引导机制,将 systemhook.dylib 强制注入目标进程地址空间
↓
④ systemhook.dylib 扫描 /var/jb/usr/lib/TweakInject 目录下的所有 *.plist
↓
⑤ 读取当前进程的 Bundle Identifier(例如 com.viper.dice),与 Filter.Bundles 数组精确匹配
↓
⑥ 匹配命中成功,systemhook 调用 dlopen("/var/jb/.../diceadbanner.dylib", RTLD_NOW)
↓
⑦ dyld 映射该 dylib 镜像,解析其符号依赖,并触发该镜像的构造函数
↓
⑧ 我在动态库中声明的 __attribute__((constructor)) 立即执行,向 ObjC 运行时安装方法替换
↓
⑨ App 后续执行到该生命周期方法时,程序计数器(PC 寄存器)直接偏转至我注入的实现
这一机制推演出的三条铁律:
- Filter 决定访问特权:plist 遗漏或 Bundle ID 书写错误,注入器将完全忽略该 dylib;
- 启动时加载特性:dylib 注入只发生在进程初始化阶段。因此修改 Tweak 必须彻底杀死进程重新冷启动,或触发
sbreload重载 SpringBoard; - 静默拒载特征:若 dylib 缺少合法代码签名,或架构与目标进程不兼容,
dlopen会在内核/dyld 层面直接静默失败,宿主进程不会产生任何崩溃日志,更不会在控制台输出显式报错。
2.2 轨二:Mach-O Header 物理注入原理
在无越狱环境(如 vphone 隔离沙盒)中,缺乏内核级挂钩能力的支撑,我只能通过修改磁盘文件将依赖固化到二进制内部:
┌────────────────────────────────────────────────────────┐
│ mach_header_64 (32 字节) │
│ magic / cputype / cpusubtype / ncmds / sizeofcmds │
├────────────────────────────────────────────────────────┤
│ Load Commands │
│ ├── LC_SEGMENT_64 (__PAGEZERO) │
│ ├── LC_SEGMENT_64 (__TEXT) │
│ ├── LC_ENCRYPTION_INFO_64 (FairPlay 声明) │
│ ├── ... │
│ └── (新增) LC_LOAD_DYLIB (72 字节) ◄── 物理追加点 │
├────────────────────────────────────────────────────────┤
│ Padding 空隙 (编译器对齐留白,约数百至数 KB) │
├────────────────────────────────────────────────────────┤
│ __TEXT Segment (代码段首个 Section) │
└────────────────────────────────────────────────────────┘
Mach-O 在 load commands 区域与第一个物理段 __TEXT 之间,为了满足内存分页对其要求(通常是 16KB 对齐),天然存在一段空白区域(Padding)。注入工具的核心逻辑即利用此间隙:
- 在
header + sizeofcmds的物理偏移处,写入一条完整的dylib_command结构体; - 将依赖库路径设置为相对路径:
@executable_path/Frameworks/diceadbanner.dylib; - 将
mach_header_64中的ncmds递增 1,同时sizeofcmds加上该命令长度(必须 8 字节对齐)。
当 App 在任何受控 iOS 系统中启动时,官方 dyld 会像加载原生依赖一样加载我注入的动态库。
2.3 Swift 命名重整与 ObjC 运行时映射
目标应用采用 Swift 开发,但其 UI 控件必须派生自系统库的 UIView。这一继承关系导致 Swift 类必然会被注册进全局 Objective-C 运行时。
Swift 编译器会对类名进行符号重整(Mangling):
_TtC 4 dice 14 LAdsBannerView
│ │ │ │ └─ 原始类名 (14 字符)
│ │ │ └──── 类名长度标记
│ │ └───────── 模块名 (dice)
│ └──────────── 模块名长度标记 (4 字符)
└──────────────── Swift 类名前缀
通过直接对二进制执行明文字符串抽取,可精确定位到该类名。在运行时通过 objc_getClass("_TtC4dice14LAdsBannerView"),我便能稳固捕获该类对象。
[!WARNING]
运行时边界法则:仅有通过@objc修饰、dynamic声明或继承自NSObject的方法,才会在 ObjC 消息调度表(Method List)中建立符号映射。纯 Swift 内部方法采用虚函数表(vtable)或内联派发,Objective-C 的 Method Swizzling 根本无法触及。这也是我必须将 Hook 挂钩点锁定在系统通用生命周期方法上的根本原因。
3. Tweak 源码工程:优雅拦截与几何物理塌陷
替换广告看似简单,若缺乏对 iOS 视图渲染机制的敬畏,极易引发内存泄漏、图层冲突或第三方 SDK 内部空指针崩溃。
3.1 挂钩点决策:为什么不是 init?
在拦截目标视图时,初学者往往习惯 Hook initWithFrame: 或构造函数:
- 构造函数陷阱:Swift 类的构造器映射为 ObjC Selector 的规则极其复杂,且常常存在多个重载,猜测签名的失误率极高;
- 生命周期必达法则:无论视图如何被实例化,当它真正要呈现于屏幕上时,必须调用
UIView的生命周期方法-didMoveToWindow与-layoutSubviews。
因此,挂钩 -didMoveToWindow 具有百分之百的命中保证。
3.2 严禁 exchange:使用 class_replaceMethod 消除全局污染
在 Objective-C 逆向中,直接调用 method_exchangeImplementations 是极度危险的违规操作:
// ❌ 错误示范:极具破坏性的全局污染
Method m = class_getInstanceMethod(targetClass, @selector(didMoveToWindow));
method_exchangeImplementations(m, myMethod);
若 _TtC4dice14LAdsBannerView 自身没有覆写 didMoveToWindow,class_getInstanceMethod 会沿着继承链一路向上回溯,直接返回 UIView 的基类实现!此时执行 exchange,相当于把整座 App 内部所有视图组件的 didMoveToWindow 全局劫持,瞬间引发灾难性连锁反应。
工程级标准范式:
Method m = class_getInstanceMethod(cls, sel);
IMP originalIMP = method_getImplementation(m);
// class_replaceMethod 会先尝试为当前子类添加该方法,若子类不存在该实现则就地添加
// 从而将影响严格限定在当前类范围内,原实现指针保存在静态变量中
class_replaceMethod(cls, sel, (IMP)DBHookedDidMoveToWindow, method_getTypeEncoding(m));
3.3 视图隐藏与 Auto Layout 物理塌陷算法
在用户点击替换横幅后,如果仅仅执行 banner.hidden = YES,页面底部会留下一大块尴尬的空白区域。要彻底“腾出空间”,必须彻底抹平其几何占用:
static void DBCollapseBanner(UIView *banner) {
if (!banner) return;
// 1. 扫描当前视图及其父视图的所有约束,将涉及高度的约束全部归零
NSMutableArray *constraints = [NSMutableArray arrayWithArray:banner.constraints];
if (banner.superview) {
[constraints addObjectsFromArray:banner.superview.constraints];
}
for (NSLayoutConstraint *c in constraints) {
BOOL involvesBanner = (c.firstItem == banner || c.secondItem == banner);
if (involvesBanner) {
if (c.firstAttribute == NSLayoutAttributeHeight ||
c.secondAttribute == NSLayoutAttributeHeight) {
c.constant = 0;
}
}
}
// 2. 针对 Frame 布局(Autoresizing)的兜底兼容
if (banner.translatesAutoresizingMaskIntoConstraints) {
CGRect frame = banner.frame;
frame.size.height = 0;
banner.frame = frame;
}
// 3. 隐藏图层并触发父容器重新排版
banner.hidden = YES;
if (banner.superview) {
[banner.superview setNeedsLayout];
[banner.superview layoutIfNeeded];
}
}
[!TIP]
AdMob 内部防护策略:拦截横幅时,切记严禁直接将原有 AdMob 广告视图从树中removeFromSuperview。许多广告聚合 SDK 内部持有弱引用并依赖定时心跳刷新,若强行释放其图层对象,后续事件分发将直接触发EXC_BAD_ACCESS崩溃。正确的做法是将原有广告子视图全部标记为hidden = YES,仅插入自定义的覆盖组件。
4. FairPlay 内存脱壳:当自动化工具集体“撒谎”
为了将上述 Tweak 打包注入未越狱的 IPA 中,必须对主二进制完成脱壳。但在实际执行中,主流脱壳工具集体翻车,逼出了更硬核的直接内存比对与打补丁方案。
4.1 工具崩溃与“静默假成功”复盘
frida-ios-dump彻底失效:社区广泛使用的 2.0 版本内部依赖旧版 Frida 的全局Module对象。而设备端安装的frida-server已升级为最新的 17.23.3,API 严重破坏,直接报错unexpected error while probing dyld of target process,根本无法提取;bagbak的虚假繁荣:运行bagbak com.viper.dice后,终端打印了完美的进度条与[info] Decrypting main app...提示,最终返回 Exit Code 0。然而将产出的二进制与设备原文件进行比对,两者的 SHA256 完全一致!工具只是照搬了磁盘文件,根本没有执行解密。
4.2 内存 vs 磁盘全量字节差分定位
为了弄清楚设备上的真实加密状况,我编写了底层 Frida 脚本,将进程内存中的代码段直接按 64KB 分块导出,与磁盘文件进行逐字节 Diff 对比:
差异分析统计报告:
全文件总大小 : 3,298,304 字节
差异字节总数 : 451,768 (占比 13.70%)
首个差异偏移 : 151,552 (0x25000) ◄── 恰好等于 LC_ENCRYPTION_INFO_64.cryptoff !
分段统计:
__TEXT 段 [0x0, 0x258000) 差异仅 4074 字节 ◄── 几乎完全等于 cryptsize (4096)
后续数据段 [0x258000, 结尾) 差异 447,694 字节 ◄── 系 __DATA/__LINKEDIT 运行时重定位,完全合规
这一实测揭示了本质事实:
App 在磁盘上绝大多数内容本就是明文(__TEXT 前半段均未被加密),只有 [0x25000, 0x26000) 这仅有的 4074 字节处于 FairPlay 硬件加密状态。
flowchart LR
Disk["磁盘上的主二进制 (未解密)"] -->|cryptid = 1| CryptOff["0x25000 处 4096 字节密文"]
Kernel["XNU 内核 + 硬件 FairPlay Key"] -->|映射解密| Memory["进程内存中的主模块 (明文)"]
Memory -->|读取 4074 字节明文| Patch["写回磁盘覆盖 0x25000"]
Patch -->|重置标志位| Fixed["置 cryptid = 0 (偏移 0x2C + 16)"]
4.3 终极验收三法则(硬指标防御)
任何脱壳流程必须通过以下三项不可伪造的硬核检验,才算真正验收合格:
| 校验层级 | 检验指令 | 预期合格标准 |
|---|---|---|
| Header 标记 | otool -l Payload/dice.app/dice | grep -A5 LC_ENCRYPTION_INFO |
cryptid 0(明确告知操作系统该文件无密文) |
| 反汇编解析 | otool -tV Payload/dice.app/dice(反汇编原密文区) |
解析出规整的 ARM64 指令(如 ldr w14, [x19, #0x38]) |
| 内存全息对齐 | __TEXT 段与 Frida 抓取的内存镜像逐字节对比 |
整段差异严格等于 1 字节(即被置为 0 的 cryptid 标志本身) |
5. 致命暗坑:深度复盘 6 个真实工程失误
在整个调试历程中,我遭遇了一系列极其耗时的阻塞性问题。将这些“痛苦的教训”逐一复盘,具有超越项目本身的通用工程指导价值。
坑 1:盲信 40 字节断言“本无加密”
- 事故经过:在反汇编检查主二进制时,我查看了
__text起点(0x4000处)的前几十个字节,看到工整的 ARM64 汇编指令,便草率断言:“这个 App 在 App Store 根本没有加密,是脱壳工具自己在空转。” - 破案真相:
cryptoff声明的加密偏移量位于0x25000(151,552 字节处),而审查的0x4000属于明文段,根本不在密文覆盖范围内! - 认知迭代:结论的强度必须匹配验证的广度。局部证据只能推导出局部结论,绝不能以外围的局部明文直接推演全盘状态。
坑 2:误将工具正常退出视为任务成功
- 事故经过:在执行
bagbak时,工具未抛出任何 Python/Node 异常,最后生成了 IPA。我直接将其投入后续注入环节,排查了数小时“为什么注入后的 App 闪退”。 - 破案真相:工具虽然运行完毕,但主二进制 SHA256 与原加密文件完全一致,密文区毫无变动。
- 认知迭代:绝对不要相信工具的自我汇报,要相信可独立交叉验证的客观状态。在涉及逆向的工程中,没有显式报错不代表成功。
坑 3:用 frida.spawn 检验越狱 Tweak 注入
- 事故经过:在真机上部署完
.deb后,为了快速自动化检验,我使用frida.spawn("com.viper.dice")启动应用并列出已加载模块,发现并没有我编写的 dylib,从而误判为“ElleKit 注入失败”,浪费了大量时间排查目录权限。 - 破案真相:Dopamine 的注入挂钩安装在系统标准的
launchd与 SpringBoard 调用链上;而frida-server的spawn走的是自己的 ptrace / posix_spawn 底层调试管道,直接绕过了系统的常规启动入口!当用户在手机屏幕上物理点击图标时,Tweak 实际上早已完美生效。 - 认知迭代:测试手段绝对不能破坏被测对象的真实物理路径。在验证系统底层挂钩时,必须使用最贴近生产环境的用户触发路径(或对已启动进程执行 attach)。
坑 4:deb 打包漏签导致静默拒载
- 事故经过:在 vphone 改包注入链条中,我认真调用了签名命令;但在真机打
.deb包时,直接复制了build/目录下的原始 dylib。结果真机上的应用无论如何都无法触发 Hook。 - 破案真相:
xcrun clang产出的 dylib 裸文件未包含任何 Code Signature Blob,Rootless 越狱下的安全加载器会静默丢弃未签名的第三方库。 - 认知迭代:流程分叉后,单一路线上的修复极易遗漏到另一路线。必须将
ldid -S固化收敛到编译的最前游(build.sh),保证所有下游消费者拿到的都是天然合规的签名产物。
坑 5:仅编译 arm64 忽略 arm64e ABI
- 事故经过:编译命令中仅配置了
-arch arm64。 - 破案真相:在 iPhone 11 Pro Max(A13 芯片)等具备 PAC(指针认证码)的机型上,SpringBoard 等系统核心进程全部以 arm64e 架构编译运行。当我在排查实验中将 Hook 挂钩到 SpringBoard 时,由于架构切片不匹配,dyld 再次执行了静默拒载。
- 认知迭代:构建目标动态库时,必须精准匹配目标进程的 ABI 要求而非泛泛的硬件设备。编译参数必须显式声明
-arch arm64 -arch arm64e。
坑 6:ssh -n 参数导致远程配置被空文件覆盖
- 事故经过:我试图通过一行命令将本地 plist 传至远程设备并覆盖旧配置:
然而在全局 SSH 默认配置或别名中隐式携带了ssh -p 2233 mobile@127.0.0.1 'cat > /tmp/target.plist' < local.plist-n(将标准输入重定向到/dev/null),导致远程cat瞬间收到 0 字节,生成了一个空文件。紧接着的sudo cp直接把真机上合法的过滤器覆盖为 0 字节损坏文件。 - 破案真相:配置损坏导致整套过滤规则完全失效。
- 认知迭代:在执行任何破坏性覆盖操作之前,必须养成前置校验状态的习惯。通过
ls -la确认文件体积,或采用管道传输前转 base64 校验,能拦截 99% 的灾难性误操作。
6. 避坑方法论:对照实验与分层验收
面对 iOS 这种充斥着“静默失败”的黑盒系统,如何建立一套高效的排错防线?本次攻关总结了两条核心方法论:
6.1 对照实验切分法(The SpringBoard Slice)
当 Tweak 无法在目标应用内加载时,切忌盲目乱改代码。此时最有效的一招是:寻找一个已知必定正确的系统锚点。
我在调试中,临时将 Filter.plist 的目标改为系统桌面:
<key>Filter</key>
<dict>
<key>Bundles</key>
<array><string>com.apple.springboard</string></array>
</dict>
执行 sbreload 后,检查 SpringBoard 进程:
- 若 dylib 成功混入 SpringBoard,则瞬间证明 ElleKit 完好、越狱环境完好、dylib 自身合法,问题必定出在目标 App 的进程特异性或 Filter 配置上;
- 若 SpringBoard 也无法加载,则问题域立刻收敛至 dylib 自身的签名与架构切片。
这一步对照实验,直接将模糊的排查范围缩减了 80%。
6.2 全流程分层验收矩阵
将验收标准贯穿到构建与部署的每一个物理断点,拒绝跨层级的模糊乐观:
flowchart TD
Step1["1. 二进制层验收"] -->|通过:otool 校验 cryptid=0、可反汇编、Thin 架构| Step2["2. 包体结构验收"]
Step2 -->|通过:Payload 目录规范、SC_Info 剔除、签名 blob 存在| Step3["3. 安装层验收"]
Step3 -->|通过:installd 成功返回、ideviceinstaller list 查见| Step4["4. 运行时加载验收"]
Step4 -->|通过:vmmap/模块列表捕获 dylib、constructor 触发| Step5["5. 业务逻辑验收"]
Step5 -->|通过:广告栏消失、标签展示、点击高度压平| Finish["交付完成"]
7. 原文链接与参考资料
- Dopamine Jailbreak 官方仓库:opa334/Dopamine (GitHub) - 适用于 iOS 15.0 - 16.6.5 的现代 Rootless 越狱方案
- ElleKit 源码与挂钩框架:evelyneee/ellekit (GitHub) - 针对 Rootless 越狱环境打造的现代化 Hook 与 Tweak 注入库
- 苹果官方开源动态链接器:Apple Open Source: dyld - 动态链接器源码与 Mach-O 运行时加载规范
- Frida 动态探针工具集:Frida: Dynamic Instrumentation Toolkit - 跨平台动态插桩与进程内存分析框架
- ARM64 指针认证(PAC)架构规范:Apple Developer: Preparing Your App to Work with Pointer Authentication - 深入理解 arm64e 架构与系统级保护机制