iOS Exploiting

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

iOS Exploit Mitigations

1. Code Signing / Runtime Signature Verification

Introduced early (iPhone OS → iOS) 这是最基本的防护之一:所有可执行代码(apps、dynamic libraries、JIT-ed code、extensions、frameworks、caches)必须由根植于 Apple 信任链的证书链进行加密签名。在运行时,在将二进制加载到内存之前(或在跨某些边界进行跳转之前),系统会检查其签名。如果代码被修改(比特翻转、打补丁)或未签名,加载会失败。

  • 阻止:利用链中“经典的 payload drop + execute”阶段;任意代码注入;修改现有二进制以插入恶意逻辑。
  • 机制细节
  • Mach-O loader(以及 dynamic linker)会检查代码页、segments、entitlements、team IDs,并确保签名覆盖文件内容。
  • 对于像 JIT caches 或动态生成代码这样的内存区域,Apple 强制要求页面被签名或通过特殊 API 验证(例如带有 code-sign 检查的 mprotect)。
  • 签名包含 entitlements 和标识符;操作系统强制某些 API 或特权能力需要特定的 entitlements,无法伪造。
Example 假设某个 exploit 在进程中获得了代码执行并尝试将 shellcode 写入 heap 并跳转到那里。在 iOS 上,该页必须被标记为 executable **并且** 满足 code-signature 约束。由于 shellcode 并未用 Apple 的证书签名,跳转会失败或者系统会拒绝将该内存区域标记为可执行。

2. CoreTrust

Introduced around iOS 14+ era (or gradually in newer devices / later iOS) CoreTrust 是在运行时对二进制(包括系统和用户二进制)执行签名验证的子系统,使用的是 Apple 的根证书,而不是依赖本地缓存的 userland trust stores。

  • 阻止:安装后篡改二进制、尝试替换或打补丁系统库或用户应用的 jailbreaking 技术;通过替换受信任二进制来欺骗系统。
  • 机制细节
  • CoreTrust 不依赖本地的信任数据库或证书缓存,而是直接引用或验证指向 Apple 根的安全链或中间证书。
  • 它保证对现有二进制的修改(例如文件系统中的改动)会被检测并拒绝。
  • 它在加载时将 entitlements、team IDs、code signing flags 和其他元数据绑定到二进制。
Example 一个 jailbreak 可能尝试用打了补丁的版本替换 SpringBoard 或 libsystem 以获得持久性。但当 OS 的 loader 或 CoreTrust 进行检查时,会发现签名不匹配(或 entitlements 被修改),并拒绝执行。

3. Data Execution Prevention (DEP / NX / W^X)

Introduced in many OSes earlier; iOS had NX-bit / w^x for a long time DEP 强制要求被标为 writable(数据)的页面为 non-executable,而被标为 executable 的页面为 non-writable。不能简单地把 shellcode 写入 heap 或 stack 区域并执行它。

  • 阻止:直接的 shellcode 执行;经典的 buffer-overflow → 跳转到注入的 shellcode。
  • 机制细节
  • MMU / memory protection flags(通过页表)强制执行这种分离。
  • 任何尝试将可写页面标记为可执行都会触发系统检查(要么被禁止,要么需要 code-sign 批准)。
  • 在许多情况下,使页面可执行需要通过 OS 的 API,并且这些 API 会强制附加约束或检查。
Example 一次 overflow 将 shellcode 写到了 heap 上。攻击者尝试 `mprotect(heap_addr, size, PROT_EXEC)` 使其可执行。但系统拒绝或者验证新的页面必须通过 code-sign 约束(而 shellcode 无法满足)。

4. Address Space Layout Randomization (ASLR)

Introduced in iOS ~4–5 era (roughly iOS 4–5 timeframe) ASLR 在每次进程启动时随机化关键内存区域的基地址:libraries、heap、stack 等。gadgets 的地址在不同运行间会移动。

  • 阻止:为 ROP/JOP 硬编码 gadget 地址;静态 exploit 链;盲目跳转到已知偏移。
  • 机制细节
  • 每个加载的 library / dynamic module 都会在随机偏移处进行 rebasing。
  • stack 和 heap 基指针会随机化(在一定熵限制内)。
  • 有时其他区域(例如 mmap 分配)也会随机化。
  • 与信息泄露 mitigations 结合使用,迫使攻击者先泄露一个地址或指针以在运行时发现基地址。
Example 一个 ROP 链期望 gadget 在 `0x….lib + offset`。但由于 `lib` 在每次运行时的重定位不同,硬编码的链会失败。exploit 必须先泄露模块的基地址,然后计算 gadget 地址。

5. Kernel Address Space Layout Randomization (KASLR)

Introduced in iOS ~ (iOS 5 / iOS 6 timeframe) 与用户态 ASLR 类似,KASLR 在引导时随机化 kernel text 和其他 kernel 结构的基地址。

  • 阻止:依赖 kernel 代码或数据固定位置的内核级 exploit;静态内核 exploit。
  • 机制细节
  • 每次引导时,kernel 的基地址会在一定范围内随机化。
  • 内核数据结构(如 task_structs、vm_map 等)也可能被重新定位或偏移。
  • 攻击者必须先泄露内核指针或利用信息泄露漏洞来计算偏移,然后才能劫持内核结构或代码。
Example 一个本地漏洞试图破坏某个内核函数指针(例如在 vtable 中),位于 `KERN_BASE + offset`。但由于 `KERN_BASE` 是未知的,攻击者必须先泄露它(例如通过 read primitive)然后再计算正确的破坏地址。

6. Kernel Patch Protection (KPP / AMCC)

Introduced in newer iOS / A-series hardware (post around iOS 15–16 era or newer chips) KPP(又名 AMCC)持续监控 kernel text 页的完整性(通过哈希或校验和)。如果检测到在允许的时间窗口之外存在篡改(补丁、inline hooks、代码修改),它会触发 kernel panic 或重启。

  • 阻止:持久化的 kernel 打补丁(修改内核指令)、inline hooks、静态函数覆盖。
  • 机制细节
  • 一个硬件或固件模块监控 kernel text 区域。
  • 它会周期性或按需对页面重新哈希并与预期值比较。
  • 如果在非良性更新窗口内发生不匹配,它会触发 panic(以避免持久恶意补丁)。
  • 攻击者必须要么避免检测窗口,要么使用合法的补丁路径。
Example 一个 exploit 试图修改某个内核函数的 prologue(例如 `memcmp`)以拦截调用。但 KPP 发现代码页的哈希不再与预期匹配并触发 kernel panic,在补丁稳定前就使设备崩溃。

7. Kernel Text Read‐Only Region (KTRR)

Introduced in modern SoCs (post ~A12 / newer hardware) KTRR 是硬件强制的机制:在引导早期一旦锁定 kernel text,它从 EL1(内核)起变为只读,防止后续对代码页的写入。

  • 阻止:在引导后对内核代码的任何修改(例如 patch、就地代码注入)在 EL1 特权级别下。
  • 机制细节
  • 在引导阶段(secure/bootloader 阶段),内存控制器或安全硬件单元将包含 kernel text 的物理页标记为只读。
  • 即使 exploit 获得了完整的内核权限,也无法写入这些页面来修改指令。
  • 要修改它们,攻击者必须先破坏引导链,或颠覆 KTRR 本身。
Example 一个权限提升 exploit 跳到 EL1 并试图向内核函数写入 trampoline(例如在 syscall handler 中)。但因为这些页面已被 KTRR 锁定为只读,写入会失败或触发 fault,因此补丁无法应用。

8. Pointer Authentication Codes (PAC)

Introduced with ARMv8.3 (hardware), Apple beginning with A12 / iOS ~12+

  • PAC 是在 ARMv8.3-A 中引入的硬件特性,用于检测指针值(返回地址、函数指针、某些数据指针)的篡改,通过将一个小的加密签名(“MAC”)嵌入指针的未使用高位中来实现。
  • 该签名(“PAC”)是在指针值外加一个 modifier(上下文值,例如 stack pointer 或一些区分数据)基础上计算的。因此相同的指针值在不同上下文下会有不同的 PAC。
  • 在使用前,会有一个 authenticate 指令检查 PAC。如果有效,PAC 会被剥离得到纯指针;如果无效,指针会被“poisoned”或触发 fault。
  • 用于生成/验证 PAC 的密钥存在于特权寄存器(EL1、kernel)中,user mode 不能直接读取这些密钥。
  • 由于许多系统并未使用 64 位指针的全部位(例如 48-bit 地址空间),上位位是“空闲的”,可用于存放 PAC 而不改变有效地址。

Architectural Basis & Key Types

  • ARMv8.3 引入了 五个 128-bit 密钥(每个通过两个 64-bit 系统寄存器实现)用于 pointer authentication。

  • APIAKey — 用于 instruction pointers(域 “I”, key A)

  • APIBKey — 第二个 instruction pointer 密钥(域 “I”, key B)

  • APDAKey — 用于 data pointers(域 “D”, key A)

  • APDBKey — 用于 data pointers(域 “D”, key B)

  • APGAKey — “generic” 密钥,用于对非指针数据或其他通用用途签名

  • 这些密钥存储在特权系统寄存器中(仅在 EL1/EL2 等可访问),user mode 无法访问。

  • PAC 是通过一个加密函数(ARM 建议使用 QARMA 作为算法)计算的,输入包括:

  1. 指针值(规范化部分)
  2. 一个 modifier(上下文值,例如 salt)
  3. 秘密密钥
  4. 一些内部 tweak 逻辑 如果生成的 PAC 与存储在指针高位的值匹配,认证就成功。

Instruction Families

命名约定为:PAC / AUT / XPAC,随后是域字母。

  • PACxx 指令用于 签名 指针并插入 PAC
  • AUTxx 指令用于 认证 + 剥离(验证并移除 PAC)
  • XPACxx 指令用于 剥离(不验证)

Domains / 后缀:

MnemonicMeaning / DomainKey / DomainExample Usage in Assembly
PACIASign instruction pointer with APIAKey“I, A”PACIA X0, X1 — sign pointer in X0 using APIAKey with modifier X1
PACIBSign instruction pointer with APIBKey“I, B”PACIB X2, X3
PACDASign data pointer with APDAKey“D, A”PACDA X4, X5
PACDBSign data pointer with APDBKey“D, B”PACDB X6, X7
PACG / PACGAGeneric (non-pointer) signing with APGAKey“G”PACGA X8, X9, X10 (sign X9 with modifier X10 into X8)
AUTIAAuthenticate APIA-signed instruction pointer & strip PAC“I, A”AUTIA X0, X1 — check PAC on X0 using modifier X1, then strip
AUTIBAuthenticate APIB domain“I, B”AUTIB X2, X3
AUTDAAuthenticate APDA-signed data pointer“D, A”AUTDA X4, X5
AUTDBAuthenticate APDB-signed data pointer“D, B”AUTDB X6, X7
AUTGAAuthenticate generic / blob (APGA)“G”AUTGA X8, X9, X10 (validate generic)
XPACIStrip PAC (instruction pointer, no validation)“I”XPACI X0 — remove PAC from X0 (instruction domain)
XPACDStrip PAC (data pointer, no validation)“D”XPACD X4 — remove PAC from data pointer in X4

有一些专用 / 别名形式:

  • PACIASPPACIA X30, SP 的简写(使用 SP 作为 modifier 对 link register 签名)
  • AUTIASPAUTIA X30, SP(使用 SP 认证 link register)
  • 还有组合形式如 RETAARETAB(认证并返回)或 BLRAA(认证并分支)存在于 ARM 扩展 / 编译器支持中。
  • 也有零 modifier 的变体:PACIZA / PACIZB,其中 modifier 隐式为零等。

Modifiers

modifier 的主要目的是将 PAC 绑定到特定上下文,这样在不同上下文中签名相同地址会得到不同的 PAC。类似于给哈希添加 salt。

因此:

  • modifier 是一个上下文值(另一个寄存器),会混入 PAC 的计算中。典型选择:stack pointer(SP)、frame pointer,或某个对象 ID。
  • 使用 SP 作为 modifier 常用于 return address signing:PAC 与特定栈帧绑定。如果尝试在不同栈帧重用 LR,则 modifier 改变,PAC 验证失败。
  • 在不同 modifier 下签名相同指针值会产生不同的 PAC。
  • modifier 不需要保密,但理想情况下不应由攻击者控制。
  • 对于没有有意义 modifier 的签名/验证指令,有些形式使用零或隐式常量。

Apple / iOS / XNU Customizations & Observations

  • Apple 的 PAC 实现包含 每次引导的 diversifiers,使得密钥或 tweak 在每次引导时变化,防止跨引导的重用。
  • 他们还包含 跨域缓解,以防止 user mode 签名的 PAC 被轻易重用于 kernel mode 等。
  • 在 Apple M1 / Apple Silicon 上,逆向工程显示存在 九种 modifier 类型 和 Apple 特有的用于密钥控制的系统寄存器。
  • Apple 在许多内核子系统中使用 PAC:返回地址签名、内核数据中的指针完整性、签名的线程上下文等。
  • Google Project Zero 展示了在强大的内核内存读/写原语下,如何在 A12 时代的设备上伪造内核 PAC(针对 A keys),但 Apple 修补了许多这些路径。
  • 在 Apple 的系统中,一些密钥是 跨内核全局的,而用户进程可能会获得每进程的 key 随机性。

PAC Bypasses

  1. Kernel-mode PAC: theoretical vs real bypasses
  • 由于 kernel PAC 密钥和逻辑受到严格控制(特权寄存器、diversifiers、域隔离),伪造任意签名的内核指针非常困难。
  • Azad 在 2020 年的 “iOS Kernel PAC, One Year Later” 报告称在 iOS 12-13 中发现了一些部分绕过(签名 gadget、重用已签名状态、未保护的间接分支),但没有通用的完全绕过方法。 bazad.github.io
  • Apple 的 “Dark Magic” 定制进一步收窄了可利用面(域切换、每-key 启用位)。 i.blackhat.com
  • 已知在 Apple silicon(M1/M2)上存在 kernel PAC bypass CVE-2023-32424,由 Zecao Cai 等人报告。 i.blackhat.com
  • 但这些绕过通常依赖于非常具体的 gadgets 或实现 bug;它们并非通用绕过。

因此 kernel PAC 被认为是 高度稳健 的,尽管并非完美。

  1. User-mode / runtime PAC bypass techniques

这些更为常见,利用 PAC 在动态链接 / 运行时框架中应用或使用的不完善。下面是几个类别及示例。

2.1 Shared Cache / A key issues

  • dyld shared cache 是一个大型预链接的系统 frameworks 和 libraries blob。由于它被广泛共享,shared cache 内的函数指针通常是“预先签名”的,并被许多进程使用。攻击者将这些已签名指针作为 “PAC oracles” 进行攻击。
  • 一些绕过技术试图提取或重用 shared cache 中的 A-key 签名指针并在 gadgets 中重用它们。
  • “No Clicks Required” 的演讲描述了构建对 shared cache 的 oracle 来推断相对地址,并结合已签名指针绕过 PAC。 saelo.github.io
  • 此外,userspace 中从 shared libraries 导入的函数指针被发现对 PAC 的保护不足,允许攻击者在不改变其签名的情况下获得函数指针。(Project Zero bug entry) bugs.chromium.org

2.2 dlsym(3) / dynamic symbol resolution

  • 一个已知的绕过方法是调用 dlsym() 来获取一个 已签名 的函数指针(用 A-key 签名,diversifier 为 zero),然后使用它。因为 dlsym 返回的是合法签名的指针,使用它可以绕过伪造 PAC 的需要。
  • Epsilon 的博客详述了如何利用这一点:调用 dlsym("someSym") 会产生一个带签名的指针并可用于间接调用。 blog.epsilon-sec.com
  • Synacktiv 的 “iOS 18.4 — dlsym considered harmful” 描述了一个 bug:在 iOS 18.4 上,某些通过 dlsym 解析的符号返回的指针签名不正确(或 diversifier 有缺陷),从而导致意外的 PAC 绕过。 Synacktiv
  • dyld 中处理 dlsym 的逻辑包括:当 result->isCode 时,会使用 __builtin_ptrauth_sign_unauthenticated(..., key_asia, 0) 对返回的指针进行签名,即上下文为零。 blog.epsilon-sec.com

因此,dlsym 经常是 user-mode PAC 绕过的向量。

2.3 Other DYLD / runtime relocations

  • DYLD loader 和动态重定位逻辑非常复杂,有时会临时将页面映射为 read/write 以执行重定位,然后再切回 read-only。攻击者会利用这些窗口。Synacktiv 的演讲描述了通过动态重定位的定时性绕过 PAC 的 “Operation Triangulation”。 Synacktiv
  • DYLD 页面现在受到 SPRR / VM_FLAGS_TPRO(一些 dyld 的保护标志)的保护。但早期版本的防护较弱。 Synacktiv
  • 在 WebKit exploit 链中,DYLD loader 常常是 PAC 绕过的目标。幻灯片提到许多 PAC 绕过都针对 DYLD loader(通过 relocation、interposer hooks)。 Synacktiv

2.4 NSPredicate / NSExpression / ObjC / SLOP

  • 在 userland exploit 链中,Objective-C runtime 方法如 NSPredicateNSExpressionNSInvocation 被用来在不明显伪造指针的情况下走私控制调用。
  • 在带 PAC 之前的旧 iOS 中,一个 exploit 使用 fake NSInvocation 对象来对受控内存调用任意 selector。使用 PAC 后需要对该技术进行修改。但 SLOP(SeLector Oriented Programming) 技术在 PAC 下也有扩展使用。 Project Zero
  • 原始的 SLOP 技术允许通过创建伪造的 invocations 链接 ObjC 调用;绕过依赖于 ISA 或 selector 指针有时并非完全受 PAC 保护。 Project Zero
  • 在 PAC 部分应用的环境中,方法 / selectors / target pointers 可能并不总是受到 PAC 保护,这就给绕过留下了空间。

Example Flow

Example Signing & Authenticating ``` ; Example: function prologue / return address protection my_func: stp x29, x30, [sp, #-0x20]! ; push frame pointer + LR mov x29, sp PACIASP ; sign LR (x30) using SP as modifier ; … body … mov sp, x29 ldp x29, x30, [sp], #0x20 ; restore AUTIASP ; authenticate & strip PAC ret

; Example: indirect function pointer stored in a struct ; suppose X1 contains a function pointer PACDA X1, X2 ; sign data pointer X1 with context X2 STR X1, [X0] ; store signed pointer

; later retrieval: LDR X1, [X0] AUTDA X1, X2 ; authenticate & strip BLR X1 ; branch to valid target

; Example: stripping for comparison (unsafe) LDR X1, [X0] XPACI X1 ; strip PAC (instruction domain) CMP X1, #some_label_address BEQ matched_label

</details>

<details>
<summary>Example</summary>
缓冲区溢出会覆盖栈上的返回地址。攻击者写入目标 gadget 地址但无法计算正确的 PAC。当函数返回时,CPU 的 `AUTIA` 指令因为 PAC 不匹配而发生故障。链条失败。
Project Zero 对 A12 (iPhone XS) 的分析展示了 Apple 如何使用 PAC,以及如果攻击者拥有内存读/写原语时伪造 PAC 的方法。
</details>


### 9. **Branch Target Identification (BTI)**
**在 ARMv8.5(较新硬件)引入**
BTI 是一项硬件特性,用于检查**间接分支目标**:当执行 `blr` 或间接调用/跳转时,目标必须以 **BTI landing pad**(`BTI j` 或 `BTI c`)开头。跳转到不包含 landing pad 的 gadget 地址会触发异常。

LLVM 的实现说明了三种 BTI 指令变体及其如何映射到分支类型。

| BTI Variant | What it permits (which branch types) | Typical placement / use case |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Targets of *call*-style indirect branches (e.g. `BLR`, or `BR` using X16/X17) | Put at entry of functions that may be called indirectly |
| **BTI J** | Targets of *jump*-style branches (e.g. `BR` used for tail calls) | Placed at the beginning of blocks reachable by jump tables or tail-calls |
| **BTI JC** | Acts as both C and J | Can be targeted by either call or jump branches |

- 在启用了 branch target enforcement 的代码中,编译器会在每个合法的间接分支目标(函数入口或可被跳转到的基本块)插入 BTI 指令(C、J 或 JC),以保证间接分支只能成功跳到这些位置。
- **直接分支 / 调用**(即固定地址的 `B`, `BL`)**不受 BTI 限制**。假设代码页是可信的且攻击者不能更改它们(因此直接分支是安全的)。
- 此外,**RET / return** 指令通常不受 BTI 限制,因为返回地址通常通过 PAC 或返回签名机制保护。

#### Mechanism and enforcement

- 当 CPU 在标记为“guarded / BTI-enabled”的页中解码到一个**间接分支(BLR / BR)**时,会检查目标地址的第一条指令是否为允许的 BTI(C、J 或 JC)。若不是,则发生**Branch Target Exception**。
- BTI 指令编码设计上重用之前为 NOPs 保留的 opcode(在早期 ARM 版本中)。因此对不支持 BTI 的硬件,这些指令将表现为 NOP,从而保证向后兼容。
- 添加 BTI 的编译器 pass 仅在必要处插入:可能被间接调用的函数或由跳转指向的基本块。
- 一些补丁和 LLVM 代码表明,BTI 并非插入到*所有*基本块——仅插入到可能成为分支目标的基本块(例如来自 switch / jump table 的目标)。

#### BTI + PAC synergy

PAC 保护指针值(源)——确保间接调用/返回链未被篡改。

BTI 则确保即使指针有效,也只能指向被正确标记的入口点。

合并后,攻击者既需要带有正确 PAC 的有效指针,又需要目标处有 BTI。此举增加了构造可用 exploit gadget 的难度。

#### Example


<details>
<summary>Example</summary>
一个利用尝试切入位于 `0xABCDEF` 的 gadget,但该地址处没有以 `BTI c` 开头。CPU 在执行 `blr x0` 时检查目标并因指令前缀不包含有效 landing pad 而故障。因此许多 gadget 在没有 BTI 前缀时变得不可用。
</details>


### 10. **Privileged Access Never (PAN) & Privileged Execute Never (PXN)**
**在更近的 ARMv8 扩展 / iOS 支持中引入(用于加固内核)**

#### PAN (Privileged Access Never)

- **PAN** 是在 **ARMv8.1-A** 引入的一项功能,阻止**特权代码**(EL1 或 EL2)读取或写入被标记为**用户可访问(EL0)**的内存,除非显式禁用 PAN。
- 设计目标:即使内核被欺骗或被攻破,也不能任意解引用用户空间指针,除非先*清除* PAN,从而降低 `ret2usr` 风格利用或滥用用户控制缓冲区的风险。
- 当 PAN 启用(PSTATE.PAN = 1)时,任何特权的 load/store 指令访问被标记为“在 EL0 可访问”的虚拟地址都会触发**权限故障**。
- 当内核确实需要合法访问用户空间内存(例如复制数据到/从用户缓冲区)时,必须**临时禁用 PAN**(或使用“非特权 load/store”指令)以允许该访问。
- 在 ARM64 的 Linux 中,PAN 支持大约在 2015 年引入:内核补丁增加了对该特性的检测,并用在访问用户内存时会清除 PAN 的变体替换了 `get_user` / `put_user` 等。

**关键细节 / 限制 / 漏洞**
- 如 Siguza 等人指出,ARM 规范中的一个实现差异(或模糊行为)意味着**执行-only 的用户映射**(`--x`)可能**不会触发 PAN**。换言之,如果用户页面标记为可执行但无读权限,内核的读取尝试可能会绕过 PAN,因为架构将“在 EL0 可访问”解释为需要可读权限,而不仅仅是可执行。这在某些实现中导致 PAN 绕过。
- 因此,如果 iOS / XNU 允许执行-only 的用户页面(如某些 JIT 或 code-cache 设置可能会),即使 PAN 启用,内核也可能意外地从这些页面读取数据。这是在某些 ARMv8+ 系统中已知的微妙可利用点。

#### PXN (Privileged eXecute Never)

- **PXN** 是页表标志(在页表项的叶或 block 项中),指示该页在特权模式下(即 EL1 执行时)**禁止执行**。
- PXN 阻止内核(或任何特权代码)跳转到或从用户空间页面执行指令,即使控制流被劫持。实际上,它阻止了内核级别将控制流重定向到用户内存的情况。
- 与 PAN 结合,确保:
1. 内核默认不能读取或写入用户空间数据(PAN)
2. 内核不能执行用户空间代码(PXN)
- 在 ARMv8 的页表格式中,叶条目有 `PXN` 位(以及用于非特权的 `UXN`)在其属性位中。

因此即使内核有一个损坏的函数指针指向用户内存并尝试分支到那里,PXN 位也会导致故障。

#### Memory-permission model & how PAN and PXN map to page table bits

要理解 PAN / PXN 的工作,需要看 ARM 的地址转换和权限模型(简化说明):

- 每个页或 block 条目都有属性字段,包括用于访问权限的 **AP[2:1]**(读/写、特权与非特权)和用于禁止执行的 **UXN / PXN** 位。
- 当 PSTATE.PAN 为 1(启用)时,硬件执行修改后的语义:对标记为“EL0 可访问”的页的特权访问将被拒绝(故障)。
- 由于前述漏洞,在一些实现中仅被标记为可执行(无读权限)的页可能不会被视为“EL0 可访问”,从而可绕过 PAN。
- 当页的 PXN 位被设置时,即使指令取自更高特权级别,执行也被禁止。

#### Kernel usage of PAN / PXN in a hardened OS (e.g. iOS / XNU)

在加固内核设计(例如 Apple 可能使用的)中:

- 内核默认启用 PAN(因此特权代码受限)。
- 在需要合法读取或写入用户缓冲区的路径(例如 syscall 缓冲复制、I/O、读/写用户指针)中,内核会临时**禁用 PAN**或使用特殊指令来覆盖。
- 完成用户数据访问后,必须重新启用 PAN。
- PXN 通过页表强制执行:用户页设置 PXN = 1(因此内核不能执行它们),内核页不设置 PXN(因此内核代码可执行)。
- 内核必须确保没有代码路径会导致执行流进入用户内存区域(那样会绕过 PXN)——因此依赖“跳转到用户可控 shellcode”的利用链会被阻断。

由于通过执行-only 页面存在 PAN 绕过,实际系统中 Apple 可能会禁用或不允许执行-only 的用户页面,或通过补丁规避规范缺陷。

#### Attack surfaces, bypasses, and mitigations

- **通过执行-only 页面绕过 PAN**:如上所述,规范存在缺口:没有读权限但可执行的用户页面在某些实现中可能不被视为“在 EL0 可访问”,因此 PAN 不会阻止内核从这些页面读取。这为攻击者提供了用“执行-only”段来传递数据的非常规路径。
- **时间窗口利用**:如果内核为一段时间禁用 PAN 比必要的时间长,竞态或恶意路径可能在该窗口内利用以执行未预期的用户内存访问。
- **忘记重新启用**:如果代码路径未能重新启用 PAN,后续的内核操作可能错误地访问用户内存。
- **PXN 配置错误**:如果页表没有对用户页设置 PXN,或错误地映射了用户代码页,内核可能被诱导去执行用户空间代码。
- **投机 / 侧信道**:类似于投机性绕过,可能存在导致 PAN / PXN 检查瞬态被违反的微架构副作用(尽管此类攻击高度依赖 CPU 设计)。
- **复杂交互**:在更高级的特性(例如 JIT、共享内存、即时生成代码区域)中,内核可能需要对某些用户映射的访问或执行给予细粒度控制;在 PAN/PXN 约束下安全设计这些路径并非易事。

#### Example

<details>
<summary>Code Example</summary>
Here are illustrative pseudo-assembly sequences showing enabling/disabling PAN around user memory access, and how a fault might occur.

// Suppose kernel entry point, PAN is enabled (privileged code cannot access user memory by default)

; Kernel receives a syscall with user pointer in X0 ; wants to read an integer from user space mov X1, X0 ; X1 = user pointer

; disable PAN to allow privileged access to user memory MSR PSTATE.PAN, #0 ; clear PAN bit, disabling the restriction

ldr W2, [X1] ; now allowed load from user address

; re-enable PAN before doing other kernel logic MSR PSTATE.PAN, #1 ; set PAN

; … further kernel work …

; Later, suppose an exploit corrupts a pointer to a user-space code page and jumps there BR X3 ; branch to X3 (which points into user memory)

; Because the target page is marked PXN = 1 for privileged execution, ; the CPU throws an exception (fault) and rejects execution

If the kernel had **not** set PXN on that user page, then the branch might succeed — which would be insecure.

如果内核没有在该用户页上设置 PXN,那么该分支可能会成功 —— 这将是不安全的。

If the kernel forgets to re-enable PAN after user memory access, it opens a window where further kernel logic might accidentally read/write arbitrary user memory.

如果内核在访问用户内存后忘记重新启用 PAN,就会出现一个窗口,后续的内核逻辑可能会意外地读取/写入任意用户内存。

If the user pointer is into an execute-only page (user page with only execute permission, no read/write), under the PAN spec bug, `ldr W2, [X1]` might **not** fault even with PAN enabled, enabling a bypass exploit, depending on implementation.

如果用户指针指向一个 execute-only 页(只有执行权限、没有读/写),在 PAN 规范的 bug 下,`ldr W2, [X1]` 即使在启用 PAN 时也可能不会 fault,从而可能根据实现允许绕过 exploit。

</details>

<details>
<summary>Example</summary>
A kernel vulnerability tries to take a user-provided function pointer and call it in kernel context (i.e. `call user_buffer`). Under PAN/PXN, that operation is disallowed or faults.
</details>

---

### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Introduced in ARMv8.5 / newer (or optional extension)**
TBI means the top byte (most-significant byte) of a 64-bit pointer is ignored by address translation. This lets OS or hardware embed **tag bits** in the pointer’s top byte without affecting the actual address.

**Introduced in ARMv8.5 / newer (or optional extension)**
TBI 意味着在地址转换时会忽略 64 位指针的最高字节(最高有效字节)。这允许操作系统或硬件在指针的最高字节中嵌入 **tag bits**,而不影响实际地址。

- TBI stands for **Top Byte Ignore** (sometimes called *Address Tagging*). It is a hardware feature (available in many ARMv8+ implementations) that **ignores the top 8 bits** (bits 63:56) of a 64-bit pointer when performing **address translation / load/store / instruction fetch**.
- 实际上,CPU 在进行地址转换时,会把指针 `0xTTxxxx_xxxx_xxxx`(其中 `TT` 为最高字节)视为 `0x00xxxx_xxxx_xxxx`,忽略(屏蔽)最高字节。最高字节可以被软件用来存放 **metadata / tag bits**。
- This gives software “free” in-band space to embed a byte of tag in each pointer without altering which memory location it refers to.
- 该机制为软件提供了“免费”的带内空间,可以在每个指针中嵌入一个字节的标签,而不改变它所指向的内存位置。
- The architecture ensures that loads, stores, and instruction fetch treat the pointer with its top byte masked (i.e. tag stripped off) before performing the actual memory access.
- 架构保证在执行实际内存访问之前,load、store 和 instruction fetch 都会将指针的最高字节屏蔽(即剥离标签)。

Thus TBI decouples the **logical pointer** (pointer + tag) from the **physical address** used for memory operations.

因此 TBI 将 **逻辑指针**(指针 + 标签)与用于内存操作的 **物理地址** 解耦。

#### Why TBI: Use cases and motivation

#### 为什么使用 TBI:用例与动机

- **Pointer tagging / metadata**: You can store extra metadata (e.g. object type, version, bounds, integrity tags) in that top byte. When you later use the pointer, the tag is ignored at hardware level, so you don’t need to strip manually for the memory access.
- **Pointer tagging / metadata**:你可以在最高字节存储额外的元数据(例如对象类型、版本、边界、完整性标签)。在后来使用指针时,硬件层会忽略该标签位,因此在进行内存访问时无需手动剥离。

- **Memory tagging / MTE (Memory Tagging Extension)**: TBI is the base hardware mechanism that MTE builds on. In ARMv8.5, the **Memory Tagging Extension** uses bits 59:56 of the pointer as a **logical tag** and checks it against an **allocation tag** stored in memory.
- **Memory tagging / MTE (Memory Tagging Extension)**:TBI 是 MTE 构建的基础硬件机制。在 ARMv8.5 中,**Memory Tagging Extension** 使用指针的 59:56 位作为 **logical tag**,并将其与存储在内存中的 **allocation tag** 进行比较核验。

- **Enhanced security & integrity**: By combining TBI with pointer authentication (PAC) or runtime checks, you can force not just the pointer value but also the tag to be correct. An attacker overwriting a pointer without the correct tag will produce a mismatched tag.
- **Enhanced security & integrity**:通过将 TBI 与 pointer authentication (PAC) 或运行时检查结合使用,可以强制不仅指针值正确,标签也必须正确。攻击者在没有正确标签的情况下覆盖指针会导致标签不匹配。

- **Compatibility**: Because TBI is optional and tag bits are ignored by hardware, existing untagged code continues to operate normally. The tag bits effectively become “don’t care” bits for legacy code.
- **Compatibility**:由于 TBI 可选且硬件会忽略标签位,现有未加标签的代码可以继续正常运行。对于旧代码来说,标签位实际上成为“无关紧要”的位。

#### Example
<details>
<summary>Example</summary>
A function pointer included a tag in its top byte (say `0xAA`). An exploit overwrites the pointer low bits but neglects the tag, so when the kernel verifies or sanitizes, the pointer fails or is rejected.
</details>

---

### 12. **Page Protection Layer (PPL)**
**Introduced in late iOS / modern hardware (iOS ~17 / Apple silicon / high-end models)** (some reports show PPL circa macOS / Apple silicon, but Apple is bringing analogous protections to iOS)

- PPL is designed as an **intra-kernel protection boundary**: even if the kernel (EL1) is compromised and has read/write capabilities, **it should not be able to freely modify** certain **sensitive pages** (especially page tables, code-signing metadata, kernel code pages, entitlements, trust caches, etc.).
- PPL 被设计为一个 **内核内部的保护边界**:即使内核 (EL1) 被攻破并获得读/写能力,**也不应能够自由修改** 某些 **敏感页面**(尤其是页表、代码签名元数据、内核代码页、entitlements、trust caches 等)。

- It effectively creates a **“kernel within the kernel”** — a smaller trusted component (PPL) with **elevated privileges** that alone can modify protected pages. Other kernel code must call into PPL routines to effect changes.
- 它实际上创建了一个“内核内的内核”——一个较小的受信任组件(PPL),拥有可以修改受保护页面的 **提升权限**。其他内核代码必须调用 PPL 例程来进行修改。

- This reduces the attack surface for kernel exploits: even with full arbitrary R/W/execute in kernel mode, exploit code must also somehow get into the PPL domain (or bypass PPL) to modify critical structures.
- 这减少了内核利用的攻击面:即使在内核模式下拥有任意读/写/执行,利用代码仍然必须以某种方式进入 PPL 域(或绕过 PPL)才能修改关键结构。

- On newer Apple silicon (A15+ / M2+), Apple is transitioning to **SPTM (Secure Page Table Monitor)**, which in many cases replaces PPL for page-table protection on those platforms.
- 在更新的 Apple silicon(A15+ / M2+)上,Apple 正在过渡到 **SPTM (Secure Page Table Monitor)**,在许多情况下 SPTM 取代了 PPL 对这些平台的页表保护功能。

Here’s how PPL is believed to operate, based on public analysis:

以下是基于公开分析对 PPL 工作方式的推测:

#### Use of APRR / permission routing (APRR = Access Permission ReRouting)

#### APRR / 权限路由 的使用(APRR = Access Permission ReRouting)

- Apple hardware uses a mechanism called **APRR (Access Permission ReRouting)**, which allows page table entries (PTEs) to contain small indices, rather than full permission bits. Those indices are mapped via APRR registers to actual permissions. This allows dynamic remapping of permissions per domain.
- Apple 硬件使用一种称为 **APRR (Access Permission ReRouting)** 的机制,它允许页表项 (PTEs) 包含小的索引,而不是完整的权限位。那些索引通过 APRR 寄存器映射到实际权限,从而允许针对不同域动态重映射权限。

- PPL leverages APRR to segregate privilege within kernel context: only the PPL domain is permitted to update the mapping between indices and effective permissions. That is, when non-PPL kernel code writes a PTE or tries to flip permission bits, the APRR logic disallows it (or enforces read-only mapping).
- PPL 利用 APRR 在内核上下文中划分特权:只有 PPL 域被允许更新索引与实际权限之间的映射。也就是说,当非 PPL 的内核代码写入 PTE 或尝试翻转权限位时,APRR 逻辑会不允许(或强制只读映射)。

- PPL code itself runs in a restricted region (e.g. `__PPLTEXT`) which is normally non-executable or non-writable until entry gates temporarily allow it. The kernel calls PPL entry points (“PPL routines”) to perform sensitive operations.
- PPL 代码本身运行在受限区域(例如 `__PPLTEXT`),该区域通常在未通过入口门控时不可执行或不可写。内核通过调用 PPL 入口点(“PPL 例程”)来执行敏感操作。

#### Gate / Entry & Exit

#### 入口/退出 门控

- When the kernel needs to modify a protected page (e.g. change permissions of a kernel code page, or modify page tables), it calls into a **PPL wrapper** routine, which does validation and then transitions into the PPL domain. Outside that domain, the protected pages are effectively read-only or non-modifiable by the main kernel.
- 当内核需要修改受保护页面(例如更改内核代码页的权限或修改页表)时,会调用一个 **PPL wrapper** 例程,该例程进行验证然后切换到 PPL 域。在该域之外,这些受保护页面对主内核来说实际上是只读或不可修改的。

- During PPL entry, the APRR mappings are adjusted so that memory pages in the PPL region are set to **executable & writable** within PPL. Upon exit, they are returned to read-only / non-writable. This ensures that only well-audited PPL routines can write to protected pages.
- 在进入 PPL 时,会调整 APRR 映射,使得 PPL 区域内的内存页面在 PPL 内部被设置为 **可执行且可写**。退出后,它们会返回只读/不可写状态。这样确保只有经过充分审计的 PPL 例程可以写入受保护页面。

- Outside PPL, attempts by kernel code to write to those protected pages will fault (permission denied) because the APRR mapping for that code domain doesn’t permit writing.
- 在 PPL 之外,内核代码尝试写入这些受保护页面会导致 fault(权限被拒绝),因为该代码域的 APRR 映射不允许写入。

#### Protected page categories

#### 受保护页面类别

The pages that PPL typically protects include:

PPL 通常保护的页面包括:

- Page table structures (translation table entries, mapping metadata)
- 页表结构(转换表项、映射元数据)
- Kernel code pages, especially those containing critical logic
- 内核代码页,尤其是包含关键逻辑的页面
- Code-sign metadata (trust caches, signature blobs)
- 代码签名元数据(trust caches、签名 blob)
- Entitlement tables, signature enforcement tables
- entitlement 表、签名强制执行表
- Other high-value kernel structures where a patch would allow bypassing signature checks or credentials manipulation
- 其他高价值的内核结构,若被打补丁可能允许绕过签名检查或操纵凭证

The idea is that even if the kernel memory is fully controlled, the attacker cannot simply patch or rewrite these pages, unless they also compromise PPL routines or bypass PPL.

其核心思想是即便内核内存被完全控制,攻击者也不能简单地补丁或重写这些页面,除非他们同时破坏 PPL 例程或绕过 PPL。

#### Known Bypasses & Vulnerabilities

#### 已知绕过与漏洞

1. **Project Zero’s PPL bypass (stale TLB trick)**

1. **Project Zero’s PPL bypass (stale TLB trick)**

- A public writeup by Project Zero describes a bypass involving **stale TLB entries**.
- Project Zero 的公开文章描述了一个涉及 **stale TLB entries** 的绕过方法。
- The idea:
- 思路如下:

1. Allocate two physical pages A and B, mark them as PPL pages (so they are protected).
1. 分配两个物理页 A 和 B,将它们标记为 PPL 页面(因此受保护)。
2. Map two virtual addresses P and Q whose L3 translation table pages come from A and B.
2. 映射两个虚拟地址 P 和 Q,它们的 L3 转换表页分别来自 A 和 B。
3. Spin a thread to continuously access Q, keeping its TLB entry alive.
3. 启动一个线程持续访问 Q,保持其 TLB 条目处于活动状态。
4. Call `pmap_remove_options()` to remove mappings starting at P; due to a bug, the code mistakenly removes the TTEs for both P and Q, but only invalidates the TLB entry for P, leaving Q’s stale entry live.
4. 调用 `pmap_remove_options()` 来移除以 P 开始的映射;由于一个 bug,代码错误地移除了 P 和 Q 的 TTEs,但只使 P 的 TLB 条目失效,留下 Q 的 stale 条目仍然有效。
5. Reuse B (page Q’s table) to map arbitrary memory (e.g. PPL-protected pages). Because the stale TLB entry still maps Q’s old mapping, that mapping remains valid for that context.
5. 重用 B(Q 的表页)去映射任意内存(例如 PPL 受保护页面)。因为 stale TLB 条目仍然映射 Q 的旧映射,该映射在该上下文中仍然有效。
6. Through this, the attacker can put writable mapping of PPL-protected pages in place without going through PPL interface.
6. 通过这种方式,攻击者可以在不经过 PPL 接口的情况下放置 PPL 受保护页面的可写映射。

- This exploit required fine control of physical mapping and TLB behavior. It demonstrates that a security boundary relying on TLB / mapping correctness must be extremely careful about TLB invalidations and mapping consistency.
- 该利用需要对物理映射和 TLB 行为有精细控制。它表明,依赖于 TLB/映射正确性的安全边界必须对 TLB 失效和映射一致性非常谨慎。

- Project Zero commented that bypasses like this are subtle and rare, but possible in complex systems. Still, they regard PPL as a solid mitigation.
- Project Zero 评论道,这类绕过手法微妙且罕见,但在复杂系统中是可能的。尽管如此,他们仍将 PPL 视为一种可靠的缓解措施。

2. **Other potential hazards & constraints**

2. **其他潜在风险与约束**

- If a kernel exploit can directly enter PPL routines (via calling the PPL wrappers), it might bypass restrictions. Thus argument validation is critical.
- 如果内核利用能够直接进入 PPL 例程(通过调用 PPL wrappers),则可能绕过限制。因此对参数的验证至关重要。
- Bugs in the PPL code itself (e.g. arithmetic overflow, boundary checks) can allow out-of-bounds modifications inside PPL. Project Zero observed that such a bug in `pmap_remove_options_internal()` was exploited in their bypass.
- PPL 代码本身的漏洞(例如算术溢出、边界检查缺失)可能允许在 PPL 内部进行越界修改。Project Zero 观察到 `pmap_remove_options_internal()` 中的此类 bug 被用于他们的绕过。
- The PPL boundary is irrevocably tied to hardware enforcement (APRR, memory controller), so it's only as strong as the hardware implementation.
- PPL 边界不可避免地依赖硬件强制(APRR、内存控制器),因此其强度取决于硬件实现。

#### Example
<details>
<summary>Code Example</summary>
Here’s a simplified pseudocode / logic showing how a kernel might call into PPL to modify protected pages:
</details>
```c
// In kernel (outside PPL domain)
function kernel_modify_pptable(pt_addr, new_entry) {
// validate arguments, etc.
return ppl_call_modify(pt_addr, new_entry)  // call PPL wrapper
}

// In PPL (trusted domain)
function ppl_call_modify(pt_addr, new_entry) {
// temporarily enable write access to protected pages (via APRR adjustments)
aprr_set_index_for_write(PPL_INDEX)
// perform the modification
*pt_addr = new_entry
// restore permissions (make pages read-only again)
aprr_restore_default()
return success
}

// If kernel code outside PPL does:
*pt_addr = new_entry  // a direct write
// It will fault because APRR mapping for non-PPL domain disallows write to that page

内核可以执行许多常规操作,但只有通过 ppl_call_* 例程才能更改受保护的映射或修补代码。

Example 内核漏洞尝试覆盖 entitlement 表,或通过修改内核签名 blob 来禁用代码签名强制。因为该页面受 PPL 保护,除非通过 PPL 接口,否则写入会被阻止。所以即使获得内核代码执行,也无法绕过代码签名约束或任意修改凭证数据。 在 iOS 17+ 上,某些设备使用 SPTM 进一步隔离由 PPL 管理的页面。

PPL → SPTM / Replacements / Future

  • 在 Apple 的现代 SoCs(A15 或更高,M2 或更高)上,Apple 支持 SPTM (Secure Page Table Monitor),它在页表保护上 取代 PPL
  • Apple 在文档中指出:“Page Protection Layer (PPL) and Secure Page Table Monitor (SPTM) enforce execution of signed and trusted code … PPL manages the page table permission overrides … Secure Page Table Monitor replaces PPL on supported platforms.”
  • SPTM 架构可能会将更多策略执行移入位于内核控制之外的更高特权的监控器,从而进一步缩小信任边界。

MTE | EMTE | MIE

下面是 EMTE 在 Apple 的 MIE 配置下如何工作的高层描述:

  1. Tag assignment
  • 当分配内存时(例如在内核或用户空间通过安全分配器),会为该块分配一个 秘密标签
  • 返回给用户或内核的指针在高位包含该标签(使用 TBI / top byte ignore mechanisms)。
  1. Tag checking on access
  • 无论何时使用指针执行加载或存储,硬件都会检查指针的标签是否与内存块的标签(分配标签)匹配。如果不匹配,会立即产生异常(因为是同步的)。
  • 由于是同步的,不存在“延迟检测”窗口。
  1. Retagging on free / reuse
  • 当内存被释放时,分配器会更改该块的标签(因此带有旧标签的指针将不再匹配)。
  • 因此,对 use-after-free 指针的访问会因为标签过时而不匹配。
  1. Neighbor-tag differentiation to catch overflows
  • 相邻的分配会被赋予不同的标签。如果缓冲区溢出溢入相邻内存,标签不匹配将导致异常。
  • 这在检测跨界的小型溢出时尤其有效。
  1. Tag confidentiality enforcement
  • Apple 必须防止标签值被 leaked(因为如果攻击者得知标签,他们可以构造带有正确标签的指针)。
  • 他们加入了保护(microarchitectural / speculative controls),以避免侧信道 leakage of tag bits。
  1. Kernel and user-space integration
  • Apple 在用户空间中使用 EMTE,同时也在内核 / OS 关键组件中使用(用以保护内核免受内存损坏)。
  • 硬件/OS 确保标签规则在内核代表用户空间执行时也适用。
Example ``` Allocate A = 0x1000, assign tag T1 Allocate B = 0x2000, assign tag T2

// pointer P points into A with tag T1 P = (T1 << 56) | 0x1000

// Valid store *(P + offset) = value // tag T1 matches allocation → allowed

// Overflow attempt: P’ = P + size_of_A (into B region) *(P’ + delta) = value → pointer includes tag T1 but memory block has tag T2 → mismatch → fault

// Free A, allocator retags it to T3 free(A)

// Use-after-free: *(P) = value → pointer still has old tag T1, memory region is now T3 → mismatch → fault

</details>

#### 限制与挑战

- **Intrablock overflows**: 如果溢出仍然在同一分配内(没有越过边界)且 tag 保持相同,tag mismatch 无法检测到它。
- **Tag width limitation**: 可用于 tag 的位数很少(例如 4 位,或很小的域)——命名空间受限。
- **Side-channel leaks**: 如果 tag 位可以被 leaked(通过 cache / speculative execution),攻击者可能会获知有效的 tag 并绕过防护。Apple 的 tag confidentiality enforcement 旨在缓解此类问题。
- **Performance overhead**: 每次 load/store 的 tag 检查会增加开销;Apple 必须对硬件进行优化以将开销降到最低。
- **Compatibility & fallback**: 在不支持 EMTE 的旧硬件或不具备支持的部分上,必须有回退机制。Apple 声称只有在设备支持的情况下才启用 MIE。
- **Complex allocator logic**: 分配器必须管理 tags、retagging、对齐边界并避免 tag 冲突。分配器逻辑中的 bug 可能引入漏洞。
- **Mixed memory / hybrid areas**: 一些内存可能仍然未打 tag(legacy),这使得互操作性更复杂。
- **Speculative / transient attacks**: 与许多微结构防护一样,speculative execution 或 micro-op 融合可能短暂绕过检查或泄露 tag 位。
- **Limited to supported regions**: Apple 可能仅在选择的高风险区域(kernel、security-critical 子系统)强制执行 EMTE,而非普遍覆盖。

---

## 与标准 MTE 的主要增强/差异

以下是 Apple 强调的改进和变化:

| Feature | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---|---|
| **Check mode** | 支持同步和异步模式。在 async 下,tag mismatch 可能被延迟报告(有滞后)| Apple 默认坚持 **同步模式**——tag mismatch 立即被捕获,不允许延迟/竞态窗口。|
| **Coverage of non-tagged memory** | 对非 tag 内存(例如 globals)的访问在某些实现中可能绕过检查 | EMTE 要求从有 tag 区域访问非 tag 内存时也验证 tag 的可知性,使通过混合分配绕过变得更难。|
| **Tag confidentiality / secrecy** | Tags 可能通过 side channels 被观察到或 leaked | Apple 增加了 **Tag Confidentiality Enforcement**,尝试防止通过 speculative side-channels 等方式泄露 tag 值。|
| **Allocator integration & retagging** | MTE 在很大程度上将分配器逻辑留给软件实现 | Apple 的 secure typed allocators(kalloc_type、xzone malloc 等)与 EMTE 集成:在内存分配或释放时,对 tags 进行精细粒度管理。|
| **Always-on by default** | 在许多平台上,MTE 是可选的或默认关闭的 | Apple 在支持的硬件(例如 iPhone 17 / A19)上默认启用 EMTE / MIE,用于 kernel 和许多用户进程。|

因为 Apple 控制硬件和软件栈,它可以严格强制执行 EMTE、避免性能陷阱并修补 side-channel 漏洞。

---

## EMTE 在实践中的工作方式(Apple / MIE)

下面是 Apple MIE 配置下 EMTE 的高层次工作描述:

1. **Tag assignment**
- 当内存被分配(例如在 kernel 或通过 secure allocators 的用户空间)时,会为该块分配一个 **secret tag**。
- 返回给用户或 kernel 的指针会在高位包含该 tag(使用 TBI / top byte ignore 机制)。

2. **Tag checking on access**
- 每当使用指针执行 load 或 store 时,硬件会检查指针的 tag 是否与内存块的 tag(allocation tag)匹配。如果不匹配,则立即 fault(因为是同步的)。
- 由于是同步的,不存在“延迟检测”的窗口。

3. **Retagging on free / reuse**
- 当内存被释放时,分配器会更改该块的 tag(因此旧的指针带有过时的 tag,在访问时将不匹配)。
- 因此 use-after-free 指针将具有陈旧的 tag 并在访问时发生 mismatch。

4. **Neighbor-tag differentiation to catch overflows**
- 相邻的分配会被赋予不同的 tag。如果缓冲区溢出溢入邻近内存,tag mismatch 会导致 fault。
- 这在捕获跨边界的小型溢出时尤其有效。

5. **Tag confidentiality enforcement**
- Apple 必须防止 tag 值被泄露(因为若攻击者获知 tag,他们可以构造带有正确 tag 的指针)。
- 他们包含了保护措施(微结构/推测执行控制)以避免 tag bits 的泄露。

6. **Kernel and user-space integration**
- Apple 不仅在用户空间使用 EMTE,也在 kernel / OS 关键组件中使用(以防护内核免受内存损坏)。
- 硬件/OS 确保即使在内核为用户空间执行操作时,tag 规则仍然适用。

因为 EMTE 被构建到 MIE 中,Apple 在关键攻击面上以同步模式使用 EMTE,而不是作为可选或调试模式。

---

## Exception handling in XNU

当发生 **exception**(例如,`EXC_BAD_ACCESS`、`EXC_BAD_INSTRUCTION`、`EXC_CRASH`、`EXC_ARM_PAC` 等)时,XNU 的 **Mach layer** 负责在将其转换为 UNIX 风格的 **signal**(如 `SIGSEGV`、`SIGBUS`、`SIGILL` 等)之前拦截它。

此过程涉及在到达用户空间或被转换为 BSD signal 之前,跨越多个层次的异常传播与处理。

### Exception Flow (High-Level)

1.  **CPU triggers a synchronous exception**(例如,无效指针解引用、PAC 失败、非法指令等)。

2.  **Low-level trap handler** 运行(`trap.c`、`exception.c` 在 XNU 源码中)。

3.  trap handler 调用 **`exception_triage()`**,这是 Mach exception 处理的核心。

4.  `exception_triage()` 决定如何路由该异常:

-   首先发送到 **thread 的 exception port**。

-   然后发送到 **task 的 exception port**。

-   然后发送到 **host 的 exception port**(通常是 `launchd` 或 `ReportCrash`)。

如果这些 port 都没有处理该异常,kernel 可能会:

-   **将其转换为 BSD signal**(用于用户空间进程)。

-   **panic**(用于内核空间的异常)。

### Core Function: `exception_triage()`

函数 `exception_triage()` 将 Mach exceptions 沿可能的处理链路逐级路由,直到某个处理者处理它或最终致命。它定义在 `osfmk/kern/exception.c`。
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);

典型调用流程:

exception_triage() └── exception_deliver() ├── exception_deliver_thread() ├── exception_deliver_task() └── exception_deliver_host()

如果全部失败 → 由 bsd_exception() 处理 → 转换为诸如 SIGSEGV 之类的信号。

异常端口

每个 Mach 对象(thread、task、host)可以注册 exception ports,异常消息会被发送到这些端口。

它们由 API 定义:

task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()

Each exception port has:

  • A mask(它希望接收哪些异常)
  • A port name(用于接收消息的 Mach port)
  • A behavior(内核如何发送该消息)
  • A flavor(要包含哪些 thread state)

Debuggers and Exception Handling

A debugger(例如 LLDB)在目标 task 或 thread 上设置一个 exception port,通常使用 task_set_exception_ports()

当发生异常时:

  • Mach 消息会发送到 debugger 进程。
  • debugger 可以决定 handle(恢复、修改寄存器、跳过指令)或 not handle 该异常。
  • 如果 debugger 不 handle,它会传播到下一级(task → host)。

Flow of EXC_BAD_ACCESS

  1. 线程解引用了无效指针 → CPU 触发 Data Abort。

  2. 内核 trap 处理器调用 exception_triage(EXC_BAD_ACCESS, ...)

  3. 发送消息到:

  • Thread port →(debugger 可以拦截 breakpoint)。

  • 如果 debugger 忽略 → Task port →(进程级处理程序)。

  • 若仍忽略 → Host port(通常是 ReportCrash)。

  1. 如果无人处理 → bsd_exception() 将其转为 SIGSEGV

PAC Exceptions

Pointer Authentication (PAC) 失败(签名不匹配)时,会触发一个 特殊的 Mach exception

  • EXC_ARM_PAC(类型)
  • Codes 可能包含细节(例如 key type、pointer type)。

如果二进制带有标志 TFRO_PAC_EXC_FATAL,内核将 PAC 失败视为 致命,并绕过 debugger 的拦截。这样做是为了防止攻击者利用 debugger 绕过 PAC 检查;该标志在 platform binaries 中启用。

Software Breakpoints

软件断点(x86 上的 int3,ARM64 上的 brk)是通过故意触发故障来实现的。debugger 通过 exception port 捕获到这个异常:

  • 修改 instruction pointer 或内存。
  • 恢复原始指令。
  • 恢复执行。

相同机制允许你“捕获”PAC 异常 —— 除非设置了 TFRO_PAC_EXC_FATAL,在这种情况下异常不会到达 debugger。

Conversion to BSD Signals

如果没有处理程序接受该异常:

  • 内核调用 task_exception_notify() → bsd_exception()

  • 这会将 Mach exceptions 映射为 signals:

Mach ExceptionSignal
EXC_BAD_ACCESSSIGSEGV or SIGBUS
EXC_BAD_INSTRUCTIONSIGILL
EXC_ARITHMETICSIGFPE
EXC_SOFTWARESIGTRAP
EXC_BREAKPOINTSIGTRAP
EXC_CRASHSIGKILL
EXC_ARM_PACSIGILL (on non-fatal)

### Key Files in XNU Source

  • osfmk/kern/exception.cexception_triage()exception_deliver_*() 的核心。
  • bsd/kern/kern_sig.c → Signal delivery 逻辑。
  • osfmk/arm64/trap.c → 低级 trap handlers。
  • osfmk/mach/exc.h → Exception codes 和结构。
  • osfmk/kern/task.c → Task exception port 的设置。

Old Kernel Heap (Pre-iOS 15 / Pre-A12 era)

内核使用 zone allocatorkalloc),分成固定大小的“zones”。每个 zone 仅存储单一大小类别的分配。

From the screenshot:

Zone NameElement SizeExample Use
default.kalloc.1616 字节非常小的内核结构体、指针。
default.kalloc.3232 字节小型结构体、对象头。
default.kalloc.6464 字节IPC messages、微小的内核缓冲区。
default.kalloc.128128 字节中等对象,如 OSObject 的部分。
default.kalloc.12801280 字节大型结构,IOSurface/图形元数据。

工作原理:

  • 每个分配请求会被 向上取整 到最近的 zone 大小。(例如,50 字节请求会落在 kalloc.64 zone。)
  • 每个 zone 中的内存保存在 free list 中 —— 被内核释放的 chunk 会回到该 zone。
  • 如果你溢出一个 64 字节 缓冲区,就会覆盖同一 zone 中的下一个对象

这就是为什么 heap spraying / feng shui 如此有效:通过喷同一大小类别的分配,你可以预测对象的邻居。

The freelist

在每个 kalloc zone 内,被释放的对象不会直接返回给系统 —— 它们进入了 freelist,这是一个可用 chunk 的链表。

  • 当一个 chunk 被释放时,内核会在该 chunk 的开头写入一个指针 → 指向同一 zone 中下一个 free chunk 的地址。

  • zone 保持一个 HEAD 指针,指向第一个 free chunk。

  • 分配总是使用当前的 HEAD:

  1. 弹出 HEAD(将该内存返回给调用者)。

  2. 更新 HEAD = HEAD->next(存储在被释放 chunk 的头部)。

  • 释放时把 chunk 推回列表:

  • freed_chunk->next = HEAD

  • HEAD = freed_chunk

因此 freelist 只是一个构建在被释放内存内部的链表。

正常状态:

Zone page (64-byte chunks for example):
[ A ] [ F ] [ F ] [ A ] [ F ] [ A ] [ F ]

Freelist view:
HEAD ──► [ F ] ──► [ F ] ──► [ F ] ──► [ F ] ──► NULL
(next ptrs stored at start of freed chunks)

利用 freelist

因为 free chunk 的前 8 个字节等于 freelist pointer,攻击者可以破坏它:

  1. Heap overflow into an adjacent freed chunk → 覆盖其“next”指针。
  2. Use-after-free write into a freed object → 覆盖其“next”指针。

接着,在下一次分配该大小的内存时:

  • allocator 会弹出被破坏的 chunk。
  • 跟随攻击者提供的“next”指针。
  • 返回指向任意内存的指针,从而实现 fake object primitives 或 targeted overwrite。

Visual example of freelist poisoning:

Before corruption:
HEAD ──► [ F1 ] ──► [ F2 ] ──► [ F3 ] ──► NULL

After attacker overwrite of F1->next:
HEAD ──► [ F1 ]
(next) ──► 0xDEAD_BEEF_CAFE_BABE  (attacker-chosen)

Next alloc of this zone → kernel hands out memory at attacker-controlled address.

This freelist design made exploitation highly effective pre-hardening: predictable neighbors from heap sprays, raw pointer freelist links, and no type separation allowed attackers to escalate UAF/overflow bugs into arbitrary kernel memory control.

Heap Grooming / Feng Shui

The goal of heap grooming is to shape the heap layout so that when an attacker triggers an overflow or use-after-free, the target (victim) object sits right next to an attacker-controlled object.
That way, when memory corruption happens, the attacker can reliably overwrite the victim object with controlled data.

Steps:

  1. Spray allocations (fill the holes)
  • Over time, the kernel heap gets fragmented: some zones have holes where old objects were freed.
  • The attacker first makes lots of dummy allocations to fill these gaps, so the heap becomes “packed” and predictable.
  1. Force new pages
  • Once the holes are filled, the next allocations must come from new pages added to the zone.
  • Fresh pages mean objects will be clustered together, not scattered across old fragmented memory.
  • This gives the attacker much better control of neighbors.
  1. Place attacker objects
  • The attacker now sprays again, creating lots of attacker-controlled objects in those new pages.
  • These objects are predictable in size and placement (since they all belong to the same zone).
  1. Free a controlled object (make a gap)
  • The attacker deliberately frees one of their own objects.
  • This creates a “hole” in the heap, which the allocator will later reuse for the next allocation of that size.
  1. Victim object lands in the hole
  • The attacker triggers the kernel to allocate the victim object (the one they want to corrupt).
  • Since the hole is the first available slot in the freelist, the victim is placed exactly where the attacker freed their object.
  1. Overflow / UAF into victim
  • Now the attacker has attacker-controlled objects around the victim.
  • By overflowing from one of their own objects (or reusing a freed one), they can reliably overwrite the victim’s memory fields with chosen values.

Why it works:

  • Zone allocator predictability: allocations of the same size always come from the same zone.
  • Freelist behavior: new allocations reuse the most recently freed chunk first.
  • Heap sprays: attacker fills memory with predictable content and controls layout.
  • End result: attacker controls where the victim object lands and what data sits next to it.

Modern Kernel Heap (iOS 15+/A12+ SoCs)

Apple hardened the allocator and made heap grooming much harder:

1. From Classic kalloc to kalloc_type

  • Before: a single kalloc.<size> zone existed for each size class (16, 32, 64, … 1280, etc.). Any object of that size was placed there → attacker objects could sit next to privileged kernel objects.
  • Now:
  • Kernel objects are allocated from typed zones (kalloc_type).
  • Each type of object (e.g., ipc_port_t, task_t, OSString, OSData) has its own dedicated zone, even if they’re the same size.
  • The mapping between object type ↔ zone is generated from the kalloc_type system at compile time.

An attacker can no longer guarantee that controlled data (OSData) ends up adjacent to sensitive kernel objects (task_t) of the same size.

2. Slabs and Per-CPU Caches

  • The heap is divided into slabs (pages of memory carved into fixed-size chunks for that zone).
  • Each zone has a per-CPU cache to reduce contention.
  • Allocation path:
  1. Try per-CPU cache.
  2. If empty, pull from the global freelist.
  3. If freelist is empty, allocate a new slab (one or more pages).
  • Benefit: This decentralization makes heap sprays less deterministic, since allocations may be satisfied from different CPUs’ caches.

3. Randomization inside zones

  • Within a zone, freed elements are not handed back in simple FIFO/LIFO order.
  • Modern XNU uses encoded freelist pointers (safe-linking like Linux, introduced ~iOS 14).
  • Each freelist pointer is XOR-encoded with a per-zone secret cookie.
  • This prevents attackers from forging a fake freelist pointer if they gain a write primitive.
  • Some allocations are randomized in their placement within a slab, so spraying doesn’t guarantee adjacency.

4. Guarded Allocations

  • Certain critical kernel objects (e.g., credentials, task structures) are allocated in guarded zones.
  • These zones insert guard pages (unmapped memory) between slabs or use redzones around objects.
  • Any overflow into the guard page triggers a fault → immediate panic instead of silent corruption.

5. Page Protection Layer (PPL) and SPTM

  • Even if you control a freed object, you can’t modify all of kernel memory:
  • PPL (Page Protection Layer) enforces that certain regions (e.g., code signing data, entitlements) are read-only even to the kernel itself.
  • On A15/M2+ devices, this role is replaced/enhanced by SPTM (Secure Page Table Monitor) + TXM (Trusted Execution Monitor).
  • These hardware-enforced layers mean attackers can’t escalate from a single heap corruption to arbitrary patching of critical security structures.
  • (Added / Enhanced): also, PAC (Pointer Authentication Codes) is used in the kernel to protect pointers (especially function pointers, vtables) so that forging or corrupting them becomes harder.
  • (Added / Enhanced): zones may enforce zone_require / zone enforcement, i.e. that an object freed can only be returned through its correct typed zone; invalid cross-zone frees may panic or be rejected. (Apple alludes to this in their memory safety posts)

6. Large Allocations

  • Not all allocations go through kalloc_type.
  • Very large requests (above ~16 KB) bypass typed zones and are served directly from kernel VM (kmem) via page allocations.
  • These are less predictable, but also less exploitable, since they don’t share slabs with other objects.

7. Allocation Patterns Attackers Target

Even with these protections, attackers still look for:

  • Reference count objects: if you can tamper with retain/release counters, you may cause use-after-free.
  • Objects with function pointers (vtables): corrupting one still yields control flow.
  • Shared memory objects (IOSurface, Mach ports): these are still attack targets because they bridge user ↔ kernel.

But — unlike before — you can’t just spray OSData and expect it to neighbor a task_t. You need type-specific bugs or info leaks to succeed.

Example: Allocation Flow in Modern Heap

Suppose userspace calls into IOKit to allocate an OSData object:

  1. Type lookupOSData maps to kalloc_type_osdata zone (size 64 bytes).
  2. Check per-CPU cache for free elements.
  • If found → return one.
  • If empty → go to global freelist.
  • If freelist empty → allocate a new slab (page of 4KB → 64 chunks of 64 bytes).
  1. Return chunk to caller.

Freelist pointer protection:

  • Each freed chunk stores the address of the next free chunk, but encoded with a secret key.
  • Overwriting that field with attacker data won’t work unless you know the key.

Comparison Table

FeatureOld Heap (Pre-iOS 15)Modern Heap (iOS 15+ / A12+)
Allocation granularityFixed size buckets (kalloc.16, kalloc.32, etc.)Size + type-based buckets (kalloc_type)
Placement predictabilityHigh (same-size objects side by side)Low (same-type grouping + randomness)
Freelist managementRaw pointers in freed chunks (easy to corrupt)Encoded pointers (safe-linking style)
Adjacent object controlEasy via sprays/frees (feng shui predictable)Hard — typed zones separate attacker objects
Kernel data/code protectionsFew hardware protectionsPPL / SPTM protect page tables & code pages, and PAC protects pointers
Allocation reuse validationNone (freelist pointers raw)zone_require / zone enforcement
Exploit reliabilityHigh with heap spraysMuch lower, requires logic bugs or info leaks
Large allocations handlingAll small allocations managed equallyLarge ones bypass zones → handled via VM

Modern Userland Heap (iOS, macOS — type-aware / xzone malloc)

In recent Apple OS versions (especially iOS 17+), Apple introduced a more secure userland allocator, xzone malloc (XZM). This is the user-space analog to the kernel’s kalloc_type, applying type awareness, metadata isolation, and memory tagging safeguards.

Goals & Design Principles

  • Type segregation / type awareness: group allocations by type or usage (pointer vs data) to prevent type confusion and cross-type reuse.
  • Metadata isolation: separate heap metadata (e.g. free lists, size/state bits) from object payloads so that out-of-bounds writes are less likely to corrupt metadata.
  • Guard pages / redzones: insert unmapped pages or padding around allocations to catch overflows.
  • Memory tagging (EMTE / MIE): work in conjunction with hardware tagging to detect use-after-free, out-of-bounds, and invalid accesses.
  • Scalable performance: maintain low overhead, avoid excessive fragmentation, and support many allocations per second with low latency.

Architecture & Components

Below are the main elements in the xzone allocator:

Segment Groups & Zones

  • Segment groups partition the address space by usage categories: e.g. data, pointer_xzones, data_large, pointer_large.
  • Each segment group contains segments (VM ranges) that host allocations for that category.
  • Associated with each segment is a metadata slab (separate VM area) that stores metadata (e.g. free/used bits, size classes) for that segment. This out-of-line (OOL) metadata ensures that metadata is not intermingled with object payloads, mitigating corruption from overflows.
  • Segments are carved into chunks (slices) which in turn are subdivided into blocks (allocation units). A chunk is tied to a specific size class and segment group (i.e. all blocks in a chunk share the same size & category).
  • For small / medium allocations, it will use fixed-size chunks; for large/huges, it may map separately.

Chunks & Blocks

  • A chunk is a region (often several pages) dedicated to allocations of one size class within a group.
  • Inside a chunk, blocks are slots available for allocations. Freed blocks are tracked via the metadata slab — e.g. via bitmaps or free lists stored out-of-line.
  • Between chunks (or within), guard slices / guard pages may be inserted (e.g. unmapped slices) to catch out-of-bounds writes.

Type / Type ID

  • Every allocation site (or call to malloc, calloc, etc.) is associated with a type identifier (a malloc_type_id_t) which encodes what kind of object is being allocated. That type ID is passed to the allocator, which uses it to select which zone / segment to serve the allocation.
  • Because of this, even if two allocations have the same size, they may go into entirely different zones if their types differ.
  • In early iOS 17 versions, not all APIs (e.g. CFAllocator) were fully type-aware; Apple addressed some of those weaknesses in iOS 18.

Allocation & Freeing Workflow

Here is a high-level flow of how allocation and deallocation operate in xzone:

  1. malloc / calloc / realloc / typed alloc is invoked with a size and type ID.
  2. The allocator uses the type ID to pick the correct segment group / zone.
  3. Within that zone/segment, it seeks a chunk that has free blocks of the requested size.
  • It may consult local caches / per-thread pools or free block lists from metadata.
  • If no free block is available, it may allocate a new chunk in that zone.
  1. The metadata slab is updated (free bit cleared, bookkeeping).
  2. If memory tagging (EMTE) is in play, the returned block gets a tag assigned, and metadata is updated to reflect its “live” state.
  3. When free() is called:
  • The block is marked as freed in metadata (via OOL slab).
  • The block may be placed into a free list or pooled for reuse.
  • Optionally, block contents may be cleared or poisoned to reduce data leaks or use-after-free exploitation.
  • The hardware tag associated with the block may be invalidated or re-tagged.
  • If an entire chunk becomes free (all blocks freed), the allocator may reclaim that chunk (unmap it or return to OS) under memory pressure.

Security Features & Hardening

These are the defenses built into modern userland xzone:

FeaturePurposeNotes
Metadata decouplingPrevent overflow from corrupting metadataMetadata lives in separate VM region (metadata slab)
Guard pages / unmapped slicesCatch out-of-bounds writesHelps detect buffer overflows rather than silently corrupting adjacent blocks
Type-based segregationPrevent cross-type reuse & type confusionEven same-size allocations from different types go to different zones
Memory Tagging (EMTE / MIE)Detect invalid access, stale references, OOB, UAFxzone works in concert with hardware EMTE in synchronous mode (“Memory Integrity Enforcement”)
Delayed reuse / poisoning / zapReduce chance of use-after-free exploitationFreed blocks may be poisoned, zeroed, or quarantined before reuse
Chunk reclamation / dynamic unmappingReduce memory waste and fragmentationEntire chunks may be unmapped when unused
Randomization / placement variationPrevent deterministic adjacencyBlocks in a chunk and chunk selection may have randomized aspects
Segregation of “data-only” allocationsSeparate allocations that don’t store pointersReduces attacker control over metadata or control fields

Interaction with Memory Integrity Enforcement (MIE / EMTE)

  • Apple’s MIE (Memory Integrity Enforcement) is the hardware + OS framework that brings Enhanced Memory Tagging Extension (EMTE) into always-on, synchronous mode across major attack surfaces.
  • xzone allocator is a fundamental foundation of MIE in user space: allocations done via xzone get tags, and accesses are checked by hardware.
  • In MIE, the allocator, tag assignment, metadata management, and tag confidentiality enforcement are integrated to ensure that memory errors (e.g. stale reads, OOB, UAF) are caught immediately, not exploited later.

If you like, I can also generate a cheat-sheet or diagram of xzone internals for your book. Do you want me to do that next?
:contentReference[oai:20]{index=20}

(Old) Physical Use-After-Free via IOSurface

ios Physical UAF - IOSurface


Ghidra Install BinDiff

Download BinDiff DMG from https://www.zynamics.com/bindiff/manual and install it.

Open Ghidra with ghidraRun and go to File –> Install Extensions, press the add button and select the path /Applications/BinDiff/Extra/Ghidra/BinExport and click OK and isntall it even if there is a version mismatch.

Using BinDiff with Kernel versions

  1. Go to the page https://ipsw.me/ and download the iOS versions you want to diff. These will be .ipsw files.
  2. Decompress until you get the bin format of the kernelcache of both .ipsw files. You have information on how to do this on:

macOS Kernel Extensions & Kernelcache

  1. Open Ghidra with ghidraRun, create a new project and load the kernelcaches.
  2. Open each kernelcache so they are automatically analyzed by Ghidra.
  3. Then, on the project Window of Ghidra, right click each kernelcache, select Export, select format Binary BinExport (v2) for BinDiff and export them.
  4. Open BinDiff, create a new workspace and add a new diff indicating as primary file the kernelcache that contains the vulnerability and as secondary file the patched kernelcache.

Finding the right XNU version

If you want to check for vulnerabilities in a specific version of iOS, you can check which XNU release version the iOS version uses at [https://www.theiphonewiki.com/wiki/kernel]https://www.theiphonewiki.com/wiki/kernel).

For example, the versions 15.1 RC, 15.1 and 15.1.1 use the version Darwin Kernel Version 21.1.0: Wed Oct 13 19:14:48 PDT 2021; root:xnu-8019.43.1~1/RELEASE_ARM64_T8006.

JSKit-Based Safari Chains and PREYHUNTER Stagers

Renderer RCE abstraction with JSKit

  • Reusable entry: Recent in-the-wild chains abused a WebKit JIT bug (patched as CVE-2023-41993) purely to gain JavaScript-level arbitrary read/write. The exploit immediately pivots into a purchased framework called JSKit, so any future Safari bug only needs to deliver the same primitive.
  • Version abstraction & PAC bypasses: JSKit bundles support for a wide range of iOS releases together with multiple, selectable Pointer Authentication Code bypass modules. The framework fingerprints the target build, selects the appropriate PAC bypass logic, and verifies every step (primitive validation, shellcode launch) before progressing.
  • Manual Mach-O mapping: JSKit parses Mach-O headers directly from memory, resolves the symbols it needs inside dyld-cached images, and can manually map additional Mach-O payloads without writing them to disk. This keeps the renderer process in-memory only and evades code-signature checks tied to filesystem artifacts.
  • Portfolio model: Debug strings such as “exploit number 7” show that the suppliers maintain multiple interchangeable WebKit exploits. Once the JS primitive matches JSKit’s interface, the rest of the chain is unchanged across campaigns.

Kernel bridge: IPC UAF -> code-sign bypass pattern

  • Kernel IPC UAF (CVE-2023-41992): The second stage, still running inside the Safari context, triggers a kernel use-after-free in IPC code, re-allocates the freed object from userland, and abuses the dangling pointers to pivot into arbitrary kernel read/write. The stage also reuses PAC bypass material previously computed by JSKit instead of re-deriving it.
  • Code-signing bypass (CVE-2023-41991): With kernel R/W available, the exploit patches the trust cache / code-signing structures so unsigned payloads execute as system. The stage then exposes a lightweight kernel R/W service to later payloads.
  • Composed pattern: This chain demonstrates a reusable recipe that defenders should expect going forward:
WebKit renderer RCE -> kernel IPC UAF -> kernel arbitrary R/W -> code-sign bypass -> unsigned system stager

PREYHUNTER helper & watcher 模块

  • Watcher anti-analysis: 一个专用的 watcher 二进制持续分析设备并在检测到研究/分析环境时中止 kill-chain。它检查 security.mac.amfi.developer_mode_status、是否存在 diagnosticd 控制台、区域设置为 USIL、越狱痕迹(例如 Cydia)、诸如 bashtcpdumpfridasshdcheckrain 的进程、移动 AV 应用(McAfee、AvastMobileSecurity、NortonMobileSecurity)、自定义 HTTP 代理设置以及自定义根 CA。任何检查失败都会阻止后续 payload 的投递。

  • Helper surveillance hooks: helper 组件通过 /tmp/helper.sock 与其他阶段通信,然后加载名为 DMHookerUMHooker 的 hook 集。这些 hook 切入 VOIP 音频路径(录音会落在 /private/var/tmp/l/voip_%lu_%u_PART.m4a),实现全系统 keylogger、在无 UI 的情况下拍照,并 hook SpringBoard 以抑制这些操作通常会触发的通知。因此 helper 在部署像 Predator 这样的更重型植入物之前,充当隐蔽的验证 + 轻度监控层。

  • HiddenDot indicator suppression in SpringBoard: 通过内核级代码注入,Predator hook 了 SBSensorActivityDataProvider._handleNewDomainData:(传感器活动的聚合点)。该 hook 将 Objective-C 的 self 指针(x0)置零,使调用变为 [nil _handleNewDomainData:newData],从而丢弃摄像头/麦克风更新并抑制绿色/橙色指示点。

  • Mach exception-based hooking flow (DMHooker): hook 是通过 EXC_BREAKPOINT + exception ports 实现的,然后使用 thread_set_state 修改寄存器并恢复执行。返回码 2 表示“继续使用已修改的线程状态”。

  • PAC-aware redirection for camera access checks: 在 mediaserverd 中,使用模式扫描(例如 memmem)定位位于 CMCapture.framework 内靠近 FigVideoCaptureSourceCreateWithSourceInfo 的私有例程。该 hook 返回 3,通过使用预签名的 PAC 缓存返回地址进行重定向,在满足 PAC 的同时绕过检查。

  • VoIP capture pipeline in mediaserverd: hook AudioConverterNewAudioConverterConvertComplexBuffer+52 以截获缓冲区,从缓冲区大小推断采样率,使用 NEON 将 float32 PCM 转换为 int16,对 4 通道进行下混至立体声,并通过 ExtAudioFileWrite() 持久化。VoIP 模块本身不抑制指示器,因此操作者必须单独启用 HiddenDot。

WebKit DFG Store-Barrier UAF + ANGLE PBO OOB (iOS 26.1)

Webkit Dfg Store Barrier Uaf Angle Oob

iMessage/Media Parser Zero-Click Chains

Imessage Media Parser Zero Click Coreaudio Pac Bypass

参考资料

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks