前言
在 Sock Port 系列文章中我们从 0 到 1 的介绍了通过 Socket UAF 拿到 tfp0 的全过程。从这篇文章开始我们将通过分析 Undecimus 介绍从 tfp0 到 jailbreak 的全过程。
单单通过 tfp0 能做的事情只是 kread, kwrite 等基础操作,要实现 rootfs read/write, kexec 等工作还需要非常复杂的步骤,本文将介绍通过 tfp0 逃出沙盒,实现 rootfs 读写的原理和过程。
The Sandbox
在 iOS 中有两个重要的内核扩展,分别是 AppleMobileFileIntegrity.kext
和 Sandbox.kext
。
Apple Mobile File Integrity
根据 The iPhone Wiki 对 AMFI 的定义[1]:
AppleMobileFileIntegrity(.kext), which can go by its full name com.apple.driver.AppleMobileFileIntegrity, is an iOS kernel extension which serves as the corner stone of iOS’s code entitlements model. It is one of the Sandbox’s (com.apple.security.sandbox) dependencies, along with com.apple.kext.AppleMatch (which, like on OS X, is responsible for parsing the Sandbox language rules).
即 AMFI.kext
是实现 iOS Code Entitlements
的基础组件,它和 AppleMatch.kext
(用于解析 Sandbox DSL) 都是 Sandbox.kext
的依赖。
可能有人对 Entitlements 并不熟悉,它代表着 App 拥有的权限。在正向开发中,如果我们为 App 开启 Capability 就会生成对应的 XML Units 插入到 App.entitlements
,某些 Capability 只有特定的证书才能生成合法签名。通过这种手段可以限制 Userland App 的权限,从而保证系统安全。
在运行时,内核扩展会注册 Mac Policy 并 hook 特定的 Mach Calls[1]:
Affectionately known as AMFI, this kext can be found in the iOS 5.0 iPod 4,1 kernel around 0x805E499C (start) and 0x805E3EE8 (Initialization function). The latter function registers a MAC policy (using the kernel exported mac_policy_register), which is used to hook various system operations and enforce Apple’s tight security policy.
根据 Wiki,AMFI 会 hook 需要 task_for_pid-allow
权限的 Mach Call[1]:
This kext recognizes the task_for_pid-allow entitlement (among others) and is responsible for hooking this Mach call, which retrieves the Mach task port associated with a BSD process identifier. Given this port, one can usurp control of the task/PID, reading and writing its memory, debugging, etc. It is therefore enabled only if the binary is digitally signed with a proper entitlement file, specifying task_for_pid-allow.
即 AMFI.kext
会识别 entitlements 中的 task_for_pid-allow
,并 Hook 相关 Mach Call,该 Mach Call 会通过 BSD 进程标识符查询特定进程的任务端口返回给调用者,使得调用者可以篡改进程的 task 或 PID, 甚至进行目标进程内存的读写和调试;而 AMFI.kext
会在调用前检查调用者的二进制是否拥有包含 task_for_pid-allow
的合法签名。
Sandbox Kext
Sandbox 的实现与 AMFI.kext
类似,也是通过 Hook 一系列的 Mach Call 并检查特定的 Policy 来保证访问的合法性。根据 Dionysus Blazakis 的 Paper: The Apple Sandbox 中的描述[2]:
Once the sandbox is initialized, function calls hooked by the TrustedBSD layer will pass
through Sandbox.kext for policy enforcement. Depending on the system call, the extension
will consult the list of rules for the current process. Some rules (such as the example given
above denying access to files under the /opt/sekret path) will require pattern matching
support. Sandbox.kext imports functions from AppleMatch.kext to perform regular expression matching on the system call argument and the policy rule that is being checked.
For example, does the file being read match the denied path /opt/sekret/.*? The other
small part of the system is the Mach messages used to carry tracing information (such as
which operations are being checked) back to userspace for logging.
上述引用主要包含了 3 个关键点:
- 当 Sandbox 被初始化后,被 TrustedBSD layer 所 Hook 的 Mach Call 会通过
Sandbox.kext
执行权限检查; Sandbox.kext
会通过AppleMatch.kext
解析规则 DSL,并生成 checklist;- 通过 checklist 进行检查,例如被读取的 file path 是否在 denied path 列表中等。
Policy 的内核表示
在进程的 proc 结构中有一个 p_ucred 成员用于存储进程的 Identifier (Process owner’s identity. (PUCL)),它相当于进程的 Passport:
1 | struct proc { |
PUCL 是一个 ucred 对象:
1 | struct ucred { |
其中 cr_label
成员指向了存储 MAC Policies 的数据结构 label
:
1 | struct label { |
l_perpolicy
数组记录了 MAC Policy 列表,AMFI 和 Sandbox 的 Policy 都会插入到相应进程的 l_perpolicy
中。
根据 Quarkslab Blogs 中的文章 Modern Jailbreaks’ Post-Exploitation,AMFI 和 Sandbox 分别插入到了 0 和 1 位置[3]:
Each l_perpolicy “slot” is used by a particular MACF module, the first one being AMFI and the second one the sandbox. LiberiOS calls ShaiHulud2ProcessAtAddr to put 0 in its second label l_perpolicy[1]. Being the label used by the sandbox (processed in the function sb_evaluate), this move will neutralize it while keeping the label used by AMFI (Apple Mobile File Integrity) l_perpolicy[0] untouched (it’s more precise and prevent useful entitlement loss).
即每个 l_perpolicy
插槽都被用于特定的 MACF 模块,第一个插槽被用于 AMFI,第二个被用于 Sandbox。LiberiOS 通过调用 ShaiHulud2ProcessAtAddr
在不修改第一个插槽的情况下将第二个插槽的指针置 0 来实现更加精准和稳定的沙盒逃逸。
Escape Now
有了 tfp0 和上面的理论基础,实现沙盒逃逸的路径变得清晰了起来,我们只需要将当前进程的 l_perpolicy[1]
修改为 0,即可逃出沙盒。
首先读取到当前进程的 label,路径为 proc->p_ucred->cr_label
,随后将索引为 1 的 Policy Slot 置 0:
1 |
|
这里说明一下 sandbox_addr
的计算:
1 | kptr_t const sandbox_addr = cr_label + 0x8 + 0x8; |
我们再回顾下 label 结构体:
1 | struct label { |
虽然 l_flags
本身只有 4 字节,但 l_perpolicy
占据了 8n 字节,为了按照最大成员对齐,l_flags
也会占据 8B,因此 cr_label + 8
指向了 l_perpolicy
,再偏移 8B 则指向 Sandbox 的 Policy Slot。
通过上述操作我们便能躲过 Sandbox.kext
对进程的沙盒相关检查,实现沙盒逃逸,接下来无论是通过 C 还是 OC 的 File API 都可以对 rootfs 进行读写。在 Undecimus Jailbreak 中以这种方式读取了 kernelcache 并确定 Kernel Slide 和关键偏移量。
我们可以通过简单实验验证沙盒逃逸成功,下面的代码读取了 kernelcache 和 Applications 目录:
1 | NSArray *extractDir(NSString *dirpath) { |
总结
本文简单介绍了通过 tfp0 实现 Sandbox Escape 的原理和过程,使得读者对 tfp0 能做的事情有一个简单认识。在接下来的文章中我们会介绍基于 tfp0 的 kexec 等利用。