ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

LiveContainer 实战指南:iOS 应用免安装运行

LiveContainer 实战指南:iOS 应用免安装运行

LiveContainer 实战指南:iOS 应用免安装运行

【免费下载链接】LiveContainerRun iOS apps without actually installing them!项目地址: https://gitcode.com/gh_mirrors/li/LiveContainer

iPhone 上能跑的应用,要么来自 App Store,要么是侧载进来的 IPA——这是条铁律。LiveContainer 把它掰开了:把任意 IPA 导入这个容器启动器,应用就能免安装直接运行,同一台设备可以同时跑两个版本的同一款 App,各自拥有独立的数据与钥匙串。如果你正在找 iOS 应用容器化多开或多版本共存方案,它是目前最主流的选择。

最短上手路径:从零到跑起来

两条路,按需选择。

路径适合谁成本
装成品 IPA只想体验、不想碰编译约 10 分钟
源码构建想改签名逻辑、学习原理半天起

装成品:官方发布页提供两个变体——独立版,以及内置 SideStore 的 LiveContainer+SideStore 版(后者可以脱离电脑刷新应用)。通过 AltStore 2.0+ 或 SideStore 0.6.0+ 安装即可。系统要求 iOS/iPadOS 15+,多任务功能需要 16+。

源码构建:

git clone https://gitcode.com/gh_mirrors/li/LiveContainer cd LiveContainer # 用 Xcode 打开,把 xcconfigs/Global.xcconfig 里 # DEVELOPMENT_TEAM[config=Debug] 改成你的团队 ID,直接编译

这里要先泼一盆冷水:README 里明确警告过,市面上出现过闭源的第三方魔改构建。你装进去的每个应用的数据(包括钥匙串里的账号密码)对宿主 App 都是完全可见的,所以只认官方构建。

一个应用从导入到跑起来,底层到底发生了什么

这是整个项目最有含金量的部分。完整流程可以切成四拍:导入 → 改写 → 加载 → 运行。你如果只关心原理,重点看这一节。

导入:IPA 其实只是个文件包

你看到的:点右上角加号,选一个 IPA,列表里就多了一个应用。

底层做的:LiveContainer 把 IPA 解包到Documents/Applications/<应用名>,在旁边写一份LCAppInfo.plist记录元数据,并在Data/Application/<UUID>下为它开一个独立数据目录。多开的关键也在这里埋下——每个容器会从 128 个键链访问组里随机分一个,这 128 个组在 entitlements.xml 里写死、签名时就绑进 App,分配逻辑在 LCAppModel.swift。📦

类比:导入应用就像开店——先租下门面(二进制文件),再配一把保险柜(数据容器 + 键链组)。门面可以有多家分店,保险柜各自独立。

改写:把"可执行文件"变成"可加载的库"

你看到的:什么都没有,但这一步不做,后面全白搭。

核心代码在 LCMachOUtils.m 的LCPatchExecSlice,改动浓缩起来就几行:

// 把可执行文件字面意义上改造成动态库 header->filetype = MH_DYLIB; // MH_EXECUTE -> MH_DYLIB header->flags &= ~MH_PIE; seg->vmaddr = 0x100000000 - 0x4000; // __PAGEZERO 段挪位 seg->vmsize = 0x4000;

三处改动,各有原因:

  • 文件类型 MH_EXECUTE → MH_DYLIB:Mach-O 头里的"证件类型"从"独立主程序"换成"动态库"。程序本身一行没变,只是换了身份——dyld 才愿意把这张镜像 mmap 进一个已经存在的进程。
  • 压缩 __PAGEZERO 段:每个应用可执行文件开头都有一段保留 4GB 虚拟地址空间的"占位段"。被加载到别的进程里时,这块空间根本给不了;不压缩的话 dyld 直接报 "out of address space"。所以把vmaddr挪到0xFFFFC000、vmsize缩到单页0x4000。好比搬进公寓前先把原住户占的整栋空地退掉。
  • 注入 TweakLoader 的加载指令:插一条LC_LOAD_DYLIB指向TweakLoader.dylib,让 tweak 在加载时就有挂载点。如果 load command 区空间不够,代码会把LC_LOAD_DYLINKER的位置回收掉复用——二进制改造里处处是这种"见缝插针"。

签名处理则是双轨的:JIT 可用时干脆绕过 codesign;JIT-less 模式下,随包的 ZSign/(zsign 的 Feather 分支魔改版)会用和 LiveContainer 相同的证书把所有可执行文件重新签一遍。

加载:一场"伪装成对方"的戏

你看到的:点应用图标,它正常启动了,而且它在所有自检测里都认为"我就是那个 App 本人"。

整个伪装流程在 LCBootstrap.m 的invokeAppMain里,主角换了四套身份文件:

  1. @executable_path。dyld 把主可执行路径缓存在内部 config 结构体里。LiveContainer 临时 hookdyld4::APIs::_NSGetExecutablePath,趁函数被调用的瞬间把mainExecutablePath字段改写成目标应用的路径,完事再恢复原函数。这里能明显看到版本敏感:iOS 15–18.3.2 字段在0x10,18.4+ 挪到了0x20,iOS 27 还额外缓存了长度。README 里还留了旧方案的遗骸——用非法指针诱发 SIGSEGV 再"做点魔法"去覆盖路径,现在已经换成了精确改字段。
  2. NSBundle.mainBundle。注意它不是走 ObjC swizzle,而是直接扫描mainBundle方法实现里的_MergedGlobals静态全局数组、把指针原地替换——因为很多代码路径绕开 ObjC 层。iOS 16、17、27 三套布局各不相同,代码里按系统版本分叉。CFBundleGetMainBundle同样处理。
  3. HOME/CFFIXED_USER_HOME指向容器目录,Caches、Cookies、Documents、SystemData四件套建好。有个细枝末节很说明问题:tmp目录用的是符号链接而不是改TMP环境变量——因为有些 App 清缓存时直接删光 tmp 目录,改了环境变量的话删完就重建不回来了(issue #1040、#1125 的教训)。
  4. 进程名与 argv[0]:NSProcessInfo的 arguments、_CFGetProgname一并改掉,Swift 侧的_NSSwiftProcessInfo连 swizzle 都给你补上了。

真正让二进制跑起来之前,还有一道关。真实入口的 trampoline 在 main.c——一个极薄的壳,dlopen 自己的 framework 后尾调用进LiveContainerMain。回到invokeAppMain:JIT 已启用时先初始化 dyld_bypass_validation.m 里的库验证绕过——hookfcntl,在F_ADDFILESIGS_RETURN时把签名覆盖范围撑成"整个文件",在F_CHECK_LV时直接回答"没问题"。这套手法源自 xpn 的 "Restoring Dyld Memory Loading",是能让未签名(或者说"签名对不上")的二进制合法进入内存的关键。

然后就是高潮:dlopen_nolock(appExecPath, RTLD_LAZY|RTLD_GLOBAL|RTLD_FIRST)。iOS 27 上如果检测到段数不匹配,还会走bypass_seg_count_check再执行。dlopen 成功后,从LC_MAINload command 里读出entryoff,拿到入口点地址,直接跳进目标应用的main()——接下来它调用UIApplicationMain,像任何原装 App 一样启动。

类比:整个过程相当于替身上台。目标应用从头到尾不知道,真正的"它"只是 LiveContainer 地址空间里的一张镜像。

运行:从此它只是个普通 iOS 应用

目标应用带着完整 UIKit 生命周期跑起来。NSUserDefaults、SecItem(键链)、NSFileManager、NSURLSessionConfiguration全被 LiveContainer/Tweaks/ 下的一组 guest hook 重定向到对应容器,保证数据落在自己的目录和键链组里。任何一步失败——dlopen 报错、找不到入口、bundle 加载失败——LCBootstrap会把错误串写进 NSUserDefaults,回到界面变成一行红字。以后启动应用看到报错,那行字就是第一现场。

进阶玩法:多任务、多开容器与外部 Tweak

多任务:一个 App 里同时开几个

效果:长按应用横幅选 Multitask,应用在 App 内虚拟窗口里运行,可以拖动缩放,还能切进系统原生画中画;iPad 上可直接用系统级原生窗口模式。

开启:安装时勾选 "Keep App Extensions" → 长按横幅点 Multitask → 需要的话在设置里把多任务设为默认启动方式。适合需要"边看边聊"或固定双开对照的人。⚠️ 注意:想给多任务窗口里的应用开 JIT,需要支持按 PID 附加的启用器(如 StikDebug),普通 JIT enabler 对不上号。实现基于 FrontBoardAppLauncher,代码在 MultitaskSupport/。

多开容器:同一应用,多把钥匙

效果:长按应用管理容器,每个容器有独立的 Documents/Container 数据目录和独立键链访问组,等于"一账号一钥匙"。

开启:应用设置里新增容器 → 每个容器登录不同账号。适合多账号用户——同一款社交软件开两个号是最典型场景。注意两点:128 个键链组是随机分配,理论上会撞组;且 README 明说 guest 容器之间不沙箱,一个容器能摸到另一个的数据,敏感信息要自己划边界。另外远程推送通知在所有容器里都不工作,这是架构性限制,别反复排查。

外部 Tweak:给容器应用打插件

效果:往 Tweaks 目录丢.dylib,启动的应用会自动带上它,相当于容器内 mini 越狱。

开启:dylib 放进Documents/Tweaks/→ 启动任意应用即自动挂载(二进制里已经注入了 TweakLoader 加载指令,加载器本身在 TweakLoader/,走 CydiaSubstrate 框架)。适合熟悉 Substrate 生态、想给容器应用改 UI 的人。注意:32 位应用必须额外在设置里选一个 32 位翻译层且强依赖 JIT;tweak 之间依赖冲突会直接把宿主应用带崩,装之前先单个验证。

我踩过的坑

"JIT was not enabled" 启动即报错。一开始以为是自己的 JIT enabler 没附加上,折腾半天才确认:iOS 26+ 上这条路基本被堵死,必须走 JIT-less 模式。解法:LiveContainer 设置里从 SideStore 导入证书("Import Certificate from SideStore"),之后应用会被 ZSign 用同一证书重签,不再依赖 JIT。

文件选择器打不开、应用请求不了本地通知权限。根因是 guest 应用在系统眼里"身份不纯",部分权限路径走不通。解法:进该应用的专属设置页,分别打开 "Fix File Picker" 和 "Fix Local Notifications",然后杀掉重开。这两个开关是 per-app 的,只对问题应用开。

多任务按钮点了没反应、画中画不出现。根因不在 LiveContainer 本身,而在装它的时候没勾 "Keep App Extensions",appex 根本没带进来。解法:重新安装并勾选,一次到位。

换个容器登录状态就丢了。根因是键链条目绑在访问组上,新容器 = 新随机组,凭据天然不跟随。这不是 bug,是 128 组随机分配设计的直接后果——记住"用哪个容器登的号",没有键链迁移这回事。

它为什么是这样设计的

为什么用 hook 而不用 wrapper?因为目标应用必须真心相信自己就是主进程——dyld 缓存的主程序路径、NSBundle.mainBundle、argv[0]、进程名,任何一处穿帮都可能被 SDK 版本检测或安全 SDK 抓出来。wrapper 方案改不了 dyld 对主可执行文件的认知,只有深入改写内部状态才行。代价是脆:每升一个 iOS 大版本,结构体偏移(mainExecutablePath在 18.4+ 换了位置)、_MergedGlobals布局(16/17/27 三套)都可能变,LCBootstrap.m 里那一大片按版本分叉的代码就是这个代价的账单。

为什么 dlopen 而不是 fork/exec?因为活着的进程没法"替换自己"。在已运行的 LiveContainer 进程里跑第二个应用,唯一的路就是把对方的二进制搬进同一地址空间。代价也是连锁的:地址空间要共享 → 才有 __PAGEZERO 压缩;签名对不上 → 才有 fcntl 库验证绕过;iOS 27 连段数都要核对 → 才有bypass_seg_count_check。每一个 patch 都是上一个选择的利息。

为什么是 128 个键链组,不多不少?entitlements 在签名时就固化了,不能无限申请访问组。128 是"够用"与"申请成本"之间的平衡点:随机分配让撞组概率足够低,实际体验接近隔离。但代价写在 README 的 limitations 里——guest 容器不沙箱、权限全局生效。这其实是整个架构的同一枚硬币:正因为"不安装"才拿到自由,也因为"不安装",数据边界只能做到"软隔离"。顺带一提,这也是 README 专门警告第三方闭源构建的原因——你的应用全部活在宿主的可达范围里。

参与项目与延伸

项目是 AGPLv3(见 LICENSE)。想读构建相关,看 xcconfigs/ 和 ZSign/;想研究窗口机制,从 MultitaskSupport/ 入手最顺。如果你的场景是一台手机同时挂着好几个账号,或者想不动主屏幕上的正式版、先试试某款应用的老版本,不妨从一个单容器场景开始跑起来。

【免费下载链接】LiveContainerRun iOS apps without actually installing them!项目地址: https://gitcode.com/gh_mirrors/li/LiveContainer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表