iOS 11.2 引入了一个内核信息泄露漏洞,可用于确定 kASLR 偏移量。该问题是新增功能 __ARM_KERNEL_PROTECT__ 带来的意外后果,它导致在使用 thread_get_state 获取线程寄存器值时,内核函数 Lel0_synchronous_vector_64_long 的地址意外出现在寄存器 x18 中。该问题是在 iOS 应用崩溃日志中出现内核指针时被发现的。
在 iOS 11.2 中,Apple 在 arm64 上引入了一项名为 __ARM_KERNEL_PROTECT__ 的功能。根据 osfmk/arm64/proc_reg.h 中的注释:
__ARM_KERNEL_PROTECT__ 是一项旨在防御潜在体系结构或微体系结构漏洞的功能,这些漏洞可能允许核心在 EL0 模式下读取/访问 EL1 独有映射。其实现方式是在核心从 EL1 模式转换到 EL0 模式时尽可能移除映射,并在核心从 EL0 模式转换回 EL1 模式时恢复这些映射。
也就是说,当从 EL1(内核模式)转换到 EL0(用户模式)时,会尽可能多地移除内核映射。这应能限制利用 Spectre 或 Meltdown 等微体系结构漏洞时针对内核内存映射的攻击面。
如果查看 XNU 版本 4570.20.62 与 4570.31.3 之间的差异,会发现文件 osfmk/arm64/locore.s 中出现了许多与 __ARM_KERNEL_PROTECT__ 相关的新引用,指向寄存器 x18。特别是,异常向量 Lel0_synchronous_vector_64(系统调用 svc #0 指令时调用的异常向量)现在如下所示:
.text
.align 7
Lel0_synchronous_vector_64:
MAP_KERNEL
BRANCH_TO_KVA_VECTOR Lel0_synchronous_vector_64_long, 8
宏 BRANCH_TO_KVA_VECTOR 定义为:
.macro BRANCH_TO_KVA_VECTOR
#if __ARM_KERNEL_PROTECT__
/*
* 通过访问每 CPU 数据查找异常向量的内核缓存表。
*/
mrs x18, TPIDR_EL1
ldr x18, [x18, ACT_CPUDATAP]
ldr x18, [x18, CPU_EXC_VECTORS]
/*
* 获取此异常的处理程序并跳转至该处。
*/
ldr x18, [x18, #($1 << 3)]
br x18
#else
b $0
#endif /* __ARM_KERNEL_PROTECT__ */
.endmacro
该宏通过将指向该函数的指针加载到寄存器 x18 中,执行间接分支跳转到真正的异常向量实现 Lel0_synchronous_vector_64_long。但请注意,此对 x18 的覆盖发生在函数 fleh_dispatch64(由 Lel0_synchronous_vector_64_long 调用)保存用户空间寄存器之前。这意味着当保存用户寄存器时,x18 实际上将是指向 Lel0_synchronous_vector_64_long 的指针,而非用户空间的原始值。
尽管 x18 在异常返回时会被清除,但在用户寄存器状态中存储内核指针会引发问题,因为 thread_get_state 可用于将保存的用户寄存器状态复制回用户空间,包括寄存器 x18 的值。线程只需调用 thread_get_state 获取自身状态并查看 x18 的报告值,即可获得 Lel0_synchronous_vector_64_long 函数的地址。这使得通过将获取到的 x18 值减去 Lel0_synchronous_vector_64_long 的静态地址来确定 kASLR 偏移量变得轻而易举。
如上所述,利用方式简单:直接调用 thread_get_state 函数,获取寄存器 x18 的值,然后减去内核函数 Lel0_synchronous_vector_64_long 的静态地址即可。
我在 2018 年 2 月 26 日发现了这个问题,当时注意到 iOS 应用崩溃日志的寄存器 x18 中出现了一个内核指针。快速检查发现,设备上每份崩溃日志的寄存器 x18 中都出现了相同的值,这表明存在严重的信息泄露。
接下来,我尝试通过实验确定寄存器 x18 的具体情况。我在一个空 iOS 应用中设置断点,并使用 lldb 读取寄存器 x18 的值,证实泄露并非仅限于崩溃的应用。随后我尝试通过内联汇编读取 x18 的值,发现获取到的值与调试器中使用 reg read x18 命令显示的值不一致。这表明泄露可能真正存在于 thread_get_state 中,而在用户空间执行时,寄存器 x18 实际上并不包含内核指针。一个快速的概念验证通过 thread_get_state 读取 x18 的值,确认该函数确实是泄露的来源。
我在 2018 年 2 月 26 日(发现当天)向 Apple 报告了该问题。
作者:Brandon Azad