不越狱,iPhone 为什么也能安装任意 IPA?聊聊 iLoader、SideStore 和 LiveContainer 的原理
最近折腾 iOS 侧载,接触到了三个挺有意思的东西:
iLoader、SideStore 和 LiveContainer。
第一次看到这套方案时,我有几个疑问:
- iPhone 不是只能安装 App Store 里的 App 吗?
- 为什么登录一个 Apple ID,就能安装自己下载的 IPA?
- 免费 Apple ID 签名为什么只有 7 天?
- SideStore 为什么能让手机自己续签?
- LiveContainer 更离谱:为什么只安装一个 App,却可以在里面运行很多 IPA?
- 所谓 JIT-Less 又是在解决什么问题?
把整个链路研究一遍之后会发现,这套方案其实没有"破解 iOS"。
恰恰相反:
它是在尽可能利用 Apple 原本提供给开发者的能力,在 iOS 的代码签名体系里寻找操作空间。
本文就从开发者的角度,把这套东西拆开看看。
一、先从一个问题开始:iPhone 为什么不能随便安装 IPA?
Android 开发者对此可能尤其有感。
Android 上拿到一个 APK:
APK
↓
允许安装未知来源应用
↓
安装
↓
运行
但 iOS 完全不是这个逻辑。
拿到一个 IPA 文件,并不意味着你就能把它安装到 iPhone 上。
因为 iOS 有一套非常严格的 Code Signing(代码签名) 机制。
简单来说,一个 App 想在真实 iPhone 上运行,需要证明两件事:
这个 App 是谁签名的?
这个签名是否允许在这台设备运行?
背后涉及:
css
Certificate
Provisioning Profile
Entitlements
App ID
Device UDID
Code Signature
因此,一个普通 IPA:
xxx.ipa
直接扔给 iPhone:
xxx.ipa
↓
iPhone
↓
❌ 不能运行
不是因为 IPA 格式有什么问题,而是:
iOS 不认可它现在的签名身份。
二、但 Apple 自己留了一扇门:开发者真机调试
如果你做过 iOS 开发,就会发现一个很明显的问题:
既然 iPhone 只能运行 Apple 认可的 App,那开发者自己写的 App 怎么调试?
总不能每改一行代码就提交一次 App Store。
所以 Apple 必须提供:
Development Signing。
正常情况下,我们在 Xcode 里:
Apple ID
↓
Xcode
↓
申请开发证书
↓
生成 Provisioning Profile
↓
给 App 签名
↓
安装到 iPhone
于是:
css
自己写的 App
↓
Development Certificate
↓
Provisioning Profile
↓
Code Signing
↓
iPhone
↓
运行
关键点来了:
这套能力并不要求 App 来自 App Store。
只要符合开发签名要求,一个开发中的 App 本来就应该可以安装到你的 iPhone。
而这正是整个侧载生态的基础。
三、iLoader:本质上是在帮你干 Xcode 的活
理解这一点之后,iLoader 就很好理解了。
很多人第一次使用 iLoader 时会感觉:
输入 Apple ID → 选择 IPA → 插上 iPhone → 居然装进去了。
看起来像是"绕过了 Apple"。
实际上它做的事情更接近:
自动化了一套类似 Xcode 真机开发部署的流程。
大概可以抽象成:
markdown
Apple
↑
│
Apple ID
│
↓
iLoader
│
┌────────┴────────┐
│ │
Development Cert Provisioning
│ │
└────────┬────────┘
↓
IPA 重签名
↓
iPhone
这里实际上存在两个独立问题。
问题一:这个 App 能不能运行?
由:
css
Apple ID
Certificate
Provisioning Profile
Code Signature
解决。
问题二:App 怎么进入手机?
由:
USB
设备配对
Apple Device Services
等设备通信机制解决。
所以使用 iLoader 时经常出现一种情况:
Apple ID 登录成功
但找不到设备
这其实一点都不矛盾。
因为:
Apple ID 登录成功 ≠ iPhone 已经和电脑建立设备通信。
一个负责"签名资格",一个负责"设备部署"。
四、为什么免费 Apple ID 只有 7 天?
到这里又会出现一个问题。
App 确实安装成功了。
但是使用免费 Apple ID 做 Personal Team 开发签名,会受到比较严格的限制,其中最明显的就是:
签名有效期很短。
常见情况下就是 7 天。
可以简单理解为:
sql
Day 1
IPA
↓
开发签名
↓
安装
↓
正常运行
Day 7+
签名/Provisioning 过期
↓
iOS 不再认可
↓
App 无法正常启动
所以如果只有 iLoader,你会陷入一个很麻烦的循环:
电脑
↓
iLoader
↓
签名
↓
安装
7 天后
电脑
↓
iLoader
↓
重新签名
↓
重新安装
偶尔装一次还行。
长期使用就很烦。
于是 SideStore 出场了。
五、SideStore:把"续签"这件事搬到手机上
SideStore 要解决的问题其实很明确:
能不能第一次借助电脑完成安装,以后让 iPhone 自己完成签名刷新?
整个结构于是变成:
第一次:
Mac / PC
│
iLoader
│
↓
iPhone
│
└── SideStore
以后:
iPhone
│
SideStore
│
↓
Apple Developer Services
│
↓
刷新签名
这就产生了一个非常有意思的"鸡生蛋"问题。
SideStore 自己也是一个 App。
而它自己一开始也没有资格运行。
怎么办?
答案就是:
先借助电脑,把 SideStore 这个"能够帮助管理侧载 App 的工具"安装进去。
之后很多事情就可以在手机端完成。
所以 iLoader 在整套体系里的角色很像:
Bootstrapping Tool ------ 引导工具。
它负责把第一个关键节点送进去。
六、真正有意思的是 LiveContainer
如果故事到 SideStore 结束,其实还不算特别神奇。
真正让我觉得有意思的是:
LiveContainer。
正常情况下,假设我们想安装三个 IPA:
css
A.ipa
B.ipa
C.ipa
传统侧载方案是:
css
iOS
│
├── App A
│
├── App B
│
└── App C
每一个都是独立 App。
意味着:
css
A → 独立签名
B → 独立签名
C → 独立签名
于是马上会碰到免费开发者账号的各种限制:
erlang
App ID 数量
活跃 App 数量
Provisioning
7 天有效期
...
LiveContainer 换了一个完全不同的思路。
它不是:
帮你安装十个 App。
而是:
安装一个 App,然后让其他 App 在这个 App 里面运行。
结构一下变成了:
css
iPhone
│
└── LiveContainer.app
│
├── A.ipa
│
├── B.ipa
│
├── C.ipa
│
└── D.ipa
从 iOS 的视角来看,真正安装的核心宿主是:
LiveContainer.app
而 A/B/C/D 则由 LiveContainer 管理并运行。
这也是名字里:
Container
的含义。
七、LiveContainer 不是虚拟机
这里特别容易产生一个误解。
看到:
一个 App 里面运行另一个 App
很多人的第一反应是:
这是不是类似 Android 模拟器?
其实不是。
它并不是:
模拟 CPU
↓
模拟 iOS
↓
运行 App
目标 App 本身仍然是:
css
ARM64 Machine Code
↓
iPhone ARM CPU
↓
iOS Framework
例如目标 App 最终还是会使用:
objectivec
UIKit
Foundation
CoreGraphics
Objective-C Runtime
这些真实的系统 Framework。
所以从大的技术方向来看,它更接近:
在一个已经合法签名的宿主环境中,加载和运行另一个 iOS App 的代码。
这也是为什么它的性能和真正的原生执行非常接近。
八、问题来了:iOS 凭什么让你加载另一份代码?
这才是整个 LiveContainer 最有意思的地方。
假设我们天真地写:
markdown
LiveContainer
│
↓
解压 xxx.ipa
│
↓
找到 Mach-O
│
↓
加载
│
↓
执行
正常情况下:
没这么简单。
因为 iOS 对 executable code 有严格限制。
这里会碰到:
css
Code Signing
AMFI
Entitlements
Sandbox
Executable Pages
Dynamic Loading
等一整套安全机制。
所以:
markdown
下载任意 ARM64 Mach-O
↓
mmap
↓
RX
↓
执行
在普通非越狱 iPhone 上并不是想干就能干。
LiveContainer 真正复杂的地方,就是:
如何在 iOS 现有安全模型允许的范围内,让目标 App 的 Mach-O 代码进入一个可执行状态,并适配宿主环境。
这就开始涉及真正偏底层的东西:
css
Mach-O
Code Signature
Dynamic Loader
Objective-C Runtime
Entitlements
App Sandbox
Provisioning Profile
如果做过 iOS Runtime 或 Mach-O 相关研究,到这里应该已经开始觉得有意思了。
九、为什么 LiveContainer 还要从 SideStore 导入证书?
我在配置 LiveContainer 时遇到了一个报错:
证书未找到,
你是否登录了内置 SideStore?
一开始我以为只是 SideStore 的账号没登录。
但继续研究会发现:
这个 Certificate 本身就是整个 JIT-Less 工作链路的重要组成部分。
关系大致是:
markdown
Apple ID
│
↓
SideStore
│
├── Signing Certificate
│
└── Provisioning Profile
│
↓
LiveContainer
│
↓
处理目标 IPA
│
↓
JIT-Less
也就是说:
LiveContainer 并不是凭空获得"执行任意代码"的能力。
它依然需要在 Apple 的:
css
Code Signing
+
Provisioning
体系下工作。
这也是为什么 SideStore 和 LiveContainer 经常一起出现。
十、JIT 又是什么?
如果接触过模拟器、虚拟机或者 JavaScript Engine,应该对 JIT 不陌生:
Just-In-Time Compilation。
简单理解,就是程序运行过程中动态生成机器码并执行。
例如:
css
Runtime
↓
生成 Machine Code
↓
写入 Memory
↓
标记 Executable
↓
CPU 执行
但是从安全角度来看,这是一项非常敏感的能力。
因为如果一个普通 App 可以:
diff
随便生成代码
+
随便执行代码
iOS 的很多安全边界都会变得困难。
所以普通 iOS App 对 JIT 的使用受到非常严格的限制。
这也是为什么很多 iOS 模拟器、虚拟机、运行时工具都特别关心:
JIT
能力。
十一、JIT-Less 的意义
LiveContainer 后来提供了一个很重要的方向:
JIT-Less。
名字已经把目的写出来了:
JIT
↓
Less
也就是:
尽量不依赖持续的 JIT 能力来运行目标 App。
因此你在 LiveContainer 里会看到:
csharp
Import Certificate from SideStore
JIT-Less Mode Diagnose
这些功能并不是几个毫无关联的设置项。
背后其实是一条完整链路:
css
SideStore
│
↓
Signing Certificate
│
↓
LiveContainer
│
↓
处理 IPA / Mach-O
│
↓
满足 Code Signing 要求
│
↓
JIT-Less Runtime
│
↓
目标 App
理解这条链路之后,LiveContainer 的很多设置就不再显得莫名其妙了。
十二、把三者放到一起看
现在我们终于可以画出完整架构:
css
Apple
│
Developer Services
↑
│
Apple ID
│
┌────────────┴────────────┐
│ │
iLoader SideStore
电脑端 Bootstrap 手机端签名管理
│ │
│ 第一次部署 │ 刷新签名
↓ ↓
┌──────────────────────────────────────────┐
│ iPhone │
│ │
│ LiveContainer │
│ │ │
│ ┌──────────┼──────────┐ │
│ │ │ │ │
│ A.ipa B.ipa C.ipa │
│ │ │ │ │
│ └──────────┼──────────┘ │
│ ↓ │
│ Runtime / JIT-Less │
│ ↓ │
│ iOS Frameworks │
│ ↓ │
│ ARM64 CPU │
│ │
└──────────────────────────────────────────┘
三者的分工其实非常清晰:
| 工具 | 核心作用 |
|---|---|
| iLoader | 解决第一次怎么把开发签名 App 安装进 iPhone |
| SideStore | 解决签名过期以及手机端刷新问题 |
| LiveContainer | 解决如何用一个宿主承载和运行多个 IPA |
如果非要打个比方:
ini
iLoader
=
负责把钥匙送进房间的人
SideStore
=
负责定期给钥匙续期的人
LiveContainer
=
拿着这把钥匙,在房间里面又建了一栋公寓的人
十三、这算不算"破解 iOS"?
我觉得这是研究完之后最有意思的一点。
直觉上:
diff
非 App Store IPA
+
免费 Apple ID
+
无需越狱
+
一个 App 运行多个 IPA
怎么看都很像"绕过系统"。
但从技术角度看,更准确的描述应该是:
在 Apple 允许的开发签名、Provisioning、代码签名和 Runtime 能力边界内,组合出一套 Apple 原本没有按照这种使用方式设计的系统。
iLoader 没有让 iOS 放弃验证签名。
SideStore 没有让 Provisioning Profile 永久有效。
LiveContainer 也没有让 iOS 完全取消 Code Signing。
恰恰相反:
整个系统从头到尾都在和 Code Signing 打交道。
这也是我觉得这套东西真正有技术价值的地方。
它不是简单地:
找到一个漏洞
↓
关掉安全检查
↓
随便运行
而更像:
研究系统规则
↓
理解规则边界
↓
组合已有能力
↓
在限制条件下实现原本很难实现的效果
这其实也是很多优秀系统软件非常典型的设计方式。
十四、从移动开发者角度,这里面值得研究什么?
如果只是为了安装 IPA,到这里其实已经够了。
但如果是 Android / Flutter / iOS 开发者,我反而觉得 LiveContainer 本身比"侧载"这件事情更值得研究。
因为继续往源码里面走,会碰到一堆平时业务开发几乎接触不到的东西:
css
Mach-O 到底是什么结构?
iOS Code Signature 存在哪里?
Provisioning Profile 到底约束了什么?
Entitlements 是如何参与权限校验的?
dyld 是怎么加载 Framework 的?
Objective-C Runtime 如何处理另一个 App 的 Class?
App Sandbox 的边界在哪里?
为什么有些 IPA 可以运行,有些完全不行?
为什么 Extension / Framework 特别容易出现兼容问题?
为什么 JIT 在 iOS 上如此敏感?
这些问题背后,其实已经不是:
"怎么安装一个 IPA?"
而是:
iOS 到底是怎么决定一段代码有没有资格在 CPU 上运行的?
当问题来到这一层,事情就开始真正有意思了。
最后
最开始折腾这套东西,我只是想:
在 iPhone 上安装一个 IPA。
最后却一路顺着:
css
IPA
↓
iLoader
↓
Apple Developer Signing
↓
Certificate
↓
Provisioning Profile
↓
SideStore
↓
LiveContainer
↓
JIT / JIT-Less
↓
Mach-O
↓
Code Signing
↓
dyld
↓
iOS Runtime
摸到了 iOS 安全体系的边缘。
这也是我觉得研究这类工具很有意思的地方。
很多看起来像"黑科技"的东西,拆开之后往往没有魔法。
只有:
系统提供了什么能力?
系统设置了什么限制?
以及:
有没有人能在这些能力与限制之间,找到一条足够巧妙的路径。