iloader 开源 iOS 侧载工具五层能力审计
【免费下载链接】iloaderUser friendly sideloader项目地址: https://gitcode.com/GitHub_Trending/iloa/iloader
iloader 是一款开源 iOS 侧载工具,对外承诺安装 SideStore、导入任意 IPA、管理开发证书与配对文件。对照源码,协议编排类能力大多兑现,但 iOS 版本判断、IPA 进度回报、Anisette 动态列表、Team 选择仍是空壳或硬编码。本文锁定 v2.4.1(package.json 与 src-tauri/Cargo.toml 版本号一致),审计范围覆盖 src-tauri/src/ 全部 Rust 模块与 src/ 前端。读完带走三样东西:一套可复现的判定口径;每条标记对应的文件与函数;补某条能力要动的仓库模块。
度量协议:三档标记与声明式边界
判定用三档符号,全部以源码可读证为准,运行行为一律不采信。
| 符号 | 判定含义 | 常见误读 |
|---|---|---|
| ● | 端到端通路可用:入口、处理、出口均有源码证据 | 不含"无缺陷"承诺 |
| ◐ | 通路存在,核心语义未实现 | 不是"差一点完成",是能用但缺关键行为 |
| ○ | 无对应实现层 | 不等于"计划中" |
| 声明项 | 状态 | 原因 |
|---|---|---|
| 就绪度百分比 | 不提供 | 仓库无口径,任何数字均为编造 |
| 工时估算 | 不提供 | 无估算文档 |
| 测试套件 | 无 | src-tauri/ 与 src/ 内未定位到#[test]或*.test.* |
| 性能与成功率数据 | 无 | 未测量;唯一的量化口径来自代码注释(见层③,属估算) |
三类陈述的区分方式:代码实证(源码可直接证实)、注释口径(代码注释给出的说法,未经实测)、数据空白(无基准无数据,缺口明说)。CONTRIBUTING.md 第一行写明 "iloader is currently undergoing a rewrite, including a UI overhaul, new supported platforms, etc.",README 末尾 Future Plans 亦指向改版看板;本文只审计 v2.4.1 现状,不把"计划中"计入结论。
层① 设备与链路:枚举通了,版本分叉是空壳
本层结论:枚举与配对可用,版本判断与断连处理未兑现。
通路走读:入口 src-tauri/src/device.rslist_devices()经 usbmuxd 枚举;选中后set_selected_device()调 src-tauri/src/pairing.rspairing_file(),字节存入DeviceInfoWithPairing,供安装与投放共用。
断链定位:
| 过程节点 | 基准行为 | 本仓库落点 |
|---|---|---|
| 设备枚举 | 列出已连接设备 | ●device.rslist_devices()并行拉取 DeviceName 与 ProductVersion |
| 配对文件生成 | 选中即得可用配对文件 | ● 17.4+ 走 RPPairing 并缓存;更低版本仅 lockdown plist |
| iOS 版本分叉 | 按真实版本选择链路 | ◐ 后端有is_ios_version_below()门;前端 src/errors.tsx 的版本判断源码标注为 TODO |
| 设备断连恢复 | 断线自动重连 | ○ 无监听逻辑,src/App.tsx 仅提供 Ctrl+R 手动刷新 |
| Wi-Fi 调试 | 可选开启 | ◐generate_lockdown_plist()写 EnableWifiDebugging |
fn is_ios_version_below(version: &str, target_major: u32, target_minor: u32) -> bool { let mut parts = version.split('.'); let major = parse_version_component(parts.next()); let minor = parse_version_component(parts.next()); (major, minor) < (target_major, target_minor) }这段证明了:后端按"前两段数字"比较 iOS 版本,是分叉的实际执行点;但同名的语义判断在前端尚未接线——src/errors.tsxgetSuggestionBlock()里只有 TODO 注释加一条剥标记的正则。
跨层传导:版本分叉缺口直接喂给层⑤的错误建议裁剪;本层自身卡在宿主 usbmuxd,get_usbmuxd()失败则全链断在第一环。
修复落点:src-tauri/src/device.rs 增加 usbmuxd 设备变更监听与自动重选(本仓库可做);src/errors.tsxgetSuggestionBlock()补真实版本判断,数据源是DeviceInfo.version字段;协议层(usbmuxd、lockdown、RemotePairing)在 idevice crate,属上游 isideload / idevice 仓库。
层② 安装与进度:三步状态机真实,进度环缺失
本层结论:SideStore 分阶段可用,IPA 安装进度无回报。
通路走读:src/App.tsx 选 IPA 调sideload_operation;src-tauri/src/sideload.rssideload()调 isideload 的install_app;install_sidestore_operation()先download()再复用sideload(),步骤事件经 src-tauri/src/operation.rs 发出,src/components/operations.ts 映射到 UI。
断链定位:
| 过程节点 | 基准行为 | 本仓库落点 |
|---|---|---|
| 任意 IPA 导入 | 选文件即装 | ●sideload_operation()→install_app() |
| IPA 传输进度 | 百分比 / 速率 | ◐install_app的fn(f32)进度回调传None,UI 只有步骤态 |
| SideStore 三步流程 | download→install→pairing 分阶段 | ●op.move_on("download","install")逐步 emit |
| 安装包缓存 | 不重复下载 | ○ 源码标注 TODO "Cache & check version to avoid re-downloading" |
| 多设备批量 | 一次多台 | ○DeviceInfoMutex为单设备 Mutex,未定位到批量入口 |
let bytes = response .bytes() .await .map_err(|e| AppError::Download(e.to_string()))?; tokio::fs::write(dest, &bytes).await.map_err(|e| { AppError::Filesystem("Failed to write downloaded file".into(), e.to_string()) })?;这段证明了:download()先把整个响应读进内存再一次性写盘,既无流式写入也无进度回报,与sideload_operation单步 UI 同属一个缺口形态。
跨层传导:进度缺口向上只能报告"install 中",无法向 UI 传递任何中间态;向下依赖层③的 Sideloader 实例与层①的配对字节。
修复落点:src-tauri/src/sideload.rssideload()中None::<fn(f32)>改为传入回调,isideload 已预留该签名,协议层不动;src-tauri/src/operation.rsOperationUpdate增加 progress 载荷;install_sidestore_operation()补缓存与版本检查(均本仓库可做)。
层③ 认证与凭据:登录链完整,服务器表写死
本层结论:登录与 2FA 可用,Anisette 层两处硬编码。
通路走读:src/AppleID.tsx 收集凭据调login_new;src-tauri/src/account.rslogin()用 isideload 的AppleAccount与RemoteV3AnisetteProvider,2FA 经 mpsc 通道 120 秒超时;证书与 App ID 的 API 命令挂在同一Sideloader上。
断链定位:
| 过程节点 | 基准行为 | 本仓库落点 |
|---|---|---|
| Apple ID 登录(含 2FA) | 主流程 | ●login()+tfa_closure,recv_timeout(120s) |
| Anisette 服务器表 | 动态发现 | ◐ src/pages/Settings.tsx 顶部源码标注 TODO "Fetch anisette list from server" |
| 客户端指纹 | 由服务端下发 | ◐set_client_info_override写死 macOS 27.0 字符串,注释自注为 "just temporary" |
| Team 选择 | 多 Team 切换 | ○ 源码标注 TODO "Team Selection" |
| Anisette 失败回退 | 自动换源 | ○ 未定位到回退链,仅手动换源或reset_anisette_state() |
// TODO: Team Selection let sideloader = SideloaderBuilder::new(dev_session, email.to_lowercase()) .machine_name("iloader".into())这段证明了:登录流程在构建 Sideloader 前没有 Team 选择分支,DeveloperSession建好后直接使用其默认 Team;服务器表侧,src/pages/Settings.tsx 注释里还留了一条 "works ~50% of the time, and doesn't work with SMS 2FA"——该口径来自注释、属估算、未经实测,本文不作事实引用。
跨层传导:登录失败即断整条安装链,层②与配对投放都拿不到凭据;向上卡点是 Anisette 服务器可用性,本仓库无自动回退。
修复落点:src/pages/Settings.tsx 的anisetteServers静态数组改为从服务端拉取;src-tauri/src/account.rslogin()的set_client_info_override覆盖等服务端开始上报 client info 后可移除;Team 选择需上游 isideload 暴露 Team 枚举接口后,在本仓库login()中加选择分支。
层④ 安全与存储:keyring 默认,降级是明文
本层结论:存储分层做了,降级分支无加密兜底。
通路走读:src-tauri/src/secure_storage.rscreate_sideloading_storage()按keyring_available()选KeyringStorage或FsStorage;src-tauri/src/pairing.rswith_pairing_storage()运行期检测 keyring 翻转重建后端;配对字节以rppairing1.1_file_{udid}缓存。
断链定位:
| 过程节点 | 基准行为 | 本仓库落点 |
|---|---|---|
| 密码 / Anisette 状态 | 系统钥匙串 | ● 默认KeyringStorage |
| keyring 不可用 | 安全降级 | ◐ 明文FsStorage,降级分支源码自注为 "This is insecure!",仅一条 warn |
| 配对文件缓存 | 按设备缓存 | ● 键rppairing1.1_file_{udid};delete_stored_rppairing()删除时会轮换 host label |
| 跨机迁移 | 备份 / 恢复 | ○ 未定位到迁移机制 |
let storage = create_sideloading_storage(app).unwrap_or_else(|e| { error!( "Failed to create sideloading storage, storing pairing file in memory: {}", e ); Box::new(InMemoryStorage::new()) });这段证明了:build_pairing_storage_entry()里存在第三档兜底——存储后端创建失败时静默落InMemoryStorage,重启即丢;而with_pairing_storage()的运行期检测说明故障有分级,安全没有分级。
跨层传导:降级路径直接影响层①缓存的配对字节(设备信任凭据)与层③的 Anisette 状态;向上被系统 keyring 可用性卡住。
修复落点:src-tauri/src/secure_storage.rscreate_sideloading_storage()降级分支加本地加密,或把实际落点同步到设置页——src/App.tsxcheckKeyring现在只设noKeyringAvailable布尔,用户看不到凭据实际落在哪。
层⑤ 错误与恢复:分类与本地化是真,版本裁剪是空
本层结论:错误分类与多语言建议可用,版本适配未接线。
通路走读:src-tauri/src/error.rsAppError共 22 个变体,From<Report>按 rootcause 链拆分类;src/errors.tsxerrorSuggestionKeys把变体映射到本地化建议 key 并按平台过滤。
断链定位:
| 过程节点 | 基准行为 | 本仓库落点 |
|---|---|---|
| 错误分类 | 区分根因 | ●error.rs逐码映射 Apple 错误 |
| 本地化建议 | 每类多条建议 | ●errors.tsx变体→key 映射,28 种语言 json |
| iOS 版本适配建议 | 按版本裁剪 | ◐getSuggestionBlock()里源码标注 TODO "actually check ios version",现状是正则剥掉[ios::...]标记 |
| 失败步骤单步重试 | 从断点继续 | ○Operation::fail()失败即整体报错,未定位到重试入口 |
&SideloadError::DeveloperError(code, _) => match code { 1102 => return AppError::Underage(report_str), _ => { return AppError::Developer(report_str); } },这段证明了:Apple 的 1102(年龄不符)与 -20209(账户锁定,同函数内AuthWithMessage分支)会被拆成独立变体,前端因此能给"无解"与"去解锁账户"两种不同建议,而不是笼统的登录失败。
跨层传导:分类错误会把用户在层②安装失败时引向错误建议;向上被层①的版本判断缺口卡住——同一个空壳在两处出现。
修复落点:src/errors.tsxgetSuggestionBlock()接真实版本数据,来源是 src-tauri/src/device.rsDeviceInfo.version;单步重试需在 src-tauri/src/operation.rsOperation::fail()加恢复事件、src/App.tsxstartOperation监听处理(本仓库可做)。
依赖排序:先修哪个由断点位置决定
先做什么由断点位置决定,不由表格行数决定。
usbmuxd 宿主服务(硬前提:不可用则整条链断在 list_devices) → 设备选中 + 配对文件(pairing.rs,17.4 分叉) → Apple 登录链(account.rs + Anisette 服务器) → Sideloader 安装(sideload.rs) 可并行:iOS 版本判断(层①⑤)/Anisette 动态列表(层③)/IPA 进度反馈(层②)/keyring 降级加密(层④) 外部依赖:isideload、idevice 仓库(认证、签名、协议层)修订锚点表:
| 锚点 | 影响等级 | 代码内位置 |
|---|---|---|
| v2.4.1(package.json 与 Cargo.toml 一致) | — | 全量现状基线 |
17.4 门(is_ios_version_below+ RPPairing 缓存) | minor | src-tauri/src/pairing.rs |
| Anisette 客户端指纹临时覆盖(注释 "just temporary") | trivial | src-tauri/src/account.rs |
| Team 选择 TODO | 未落地 | src-tauri/src/account.rslogin() |
上表是代码内可追溯的全部;仓库无 revision history 文档。
复现与排查:命令、日志入口与排查清单
以下命令在仓库根目录直接运行,参数取自 package.json 与 src-tauri/Cargo.toml:
bun i—— 验证前端依赖可解析。bunx tauri dev—— 验证热重载下前后端命令注册是否可通(命令清单见 src-tauri/src/lib.rsgenerate_handler!)。cargo build --manifest-path src-tauri/Cargo.toml—— 验证 Rust 后端独立编译。git clone https://gitcode.com/GitHub_Trending/iloa/iloader—— 需要本地克隆时仅此一次。
日志入口:Settings 页 View Logs,快捷键 Ctrl+L / ⌘L(src/pages/Settings.tsx 的 keydown 监听);文件日志按平台落在%APPDATA%、~/Library/Application Support、~/.local/share下me.nabdev.iloader/logs(README.md Troubleshooting 一节)。
按源码失败链排序的排查清单:
- usbmuxd → 先确认宿主服务在跑(Windows 装 iTunes,macOS 自带,Linux 走包管理器)→ 再看物理连接 → 最后重选设备;
get_usbmuxd()是第一条会断的链。 - device_coms → 先看设备是否点了"信任这台电脑" → 再看 USB 口与线 → 最后换线重插,重跑
list_devices。 - remote_pairing / lockdown_pairing → 先看 17.4+ 设备首次配对是否完成 → 再看
delete_stored_rppairing是否清过缓存 → 最后重新set_selected_device。 - keyring / storage → 先看设置页 "Don't use keyring" 开关状态 → 再看系统钥匙串是否可用 → 最后确认凭据实际落点(当前 UI 未展示)。
- anisette → 先在 Settings 换服务器 → 再点 Reset Anisette State 清本地状态 → 最后确认所选服务器支持当前 2FA 方式。
- not_enough_app_ids / max_apps → 先看 Apple 账户配额 → 再释放不用的 App ID(AppIds 页可删)→ Apple 侧限制工具无法绕过。
- account_locked → 确认 -20209 为 Apple 锁号 → 走 Apple 解锁流程 → 解锁后重新登录。
- underage(1102)→ 确认年龄不符判定 → 无工具侧解法 → 记录日志即可。
引用纪律:三条核查规则
- 标记密度不等于可用度。● 集中在协议编排(枚举、分类、状态机),能否真正跑通取决于宿主前提(usbmuxd)、外部依赖(Anisette 服务器、isideload/idevice 协议层)与明确 TODO(Team 选择、版本判断)三层,数表格行数不产生结论。
- "未测量"不等于"不行"。没有测试套件与基准,意味着所有正确性结论都是代码可读证的推断而非运行事实;层③那条约一半命中率的数字来自注释、属估算,引用时必须与运行事实分开表述。
- 本文每行结论都锚定到文件 + 函数 + 注释:pairing.rs 的
is_ios_version_below、secure_storage.rs 的降级 warn、errors.tsx 的 TODO。下一个审计者不需要信任本文,打开这些位置即可逐条复核。
【免费下载链接】iloaderUser friendly sideloader项目地址: https://gitcode.com/GitHub_Trending/iloa/iloader
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考