原谅MIE?
摘要
一个三人团队在人工智能辅助下,利用两个漏洞和一个巧妙的想法,在五天内绕过了苹果的内存完整性执行(MIE)机制,展示了苹果基于硬件的内核内存安全栈中存在的一个重大漏洞。
暂无内容
查看缓存全文
缓存时间: 2026/05/23 21:34
# 请原谅 MIE?- ironPeak 博客 来源:https://ironpeak.be/blog/bypassing-apple-mie/ #### 请原谅 MIE?2026年5月23日 周六 #### 请原谅 MIE?2026年5月23日 周六 --- **Apple 的内存完整性强制(MIE)绝非儿戏。历经五年设计、全新 M5 芯片、内核堆上的硬件内存标签、内核核心数据的硬件锁定只读区域,以及一个位于内核之上的特权监视器,它拒绝所有未经授权的页表修改。这是任何消费级操作系统所推出过的最严肃的内核内存安全堆栈。而它仍然被绕过了。一个三人团队带着一个 AI 助手,用两个漏洞和一个巧妙的想法,在五天内就攻破了它。以下是我对他们如何做到的简要说明,无需博士学位即可理解。** 如果你想直接跳转: - 一分钟看懂漏洞 (https://ironpeak.be/blog/bypassing-apple-mie/#the-bug-in-60-seconds) - 两条指令的修复 (https://ironpeak.be/blog/bypassing-apple-mie/#the-fix-in-two-instructions) - 给防御者 (https://ironpeak.be/blog/bypassing-apple-mie/#for-defenders) - 给漏洞利用编写者 (https://ironpeak.be/blog/bypassing-apple-mie/#for-exploit-writers) ### 为什么内存安全很重要 几乎所有你读过的“你的 iPhone 被黑”的故事,归根结底都是同一个原因:一个内存错误。一个指向错误位置的指针。一个多写了一个字节的缓冲区。一个在被释放后又被重复使用的结构体。不是用户点击了可疑链接,也不是密码被盗。而是内核从某块内存读取数据,并且过于信任了它。Pegasus,那个最终落在全球记者和活动家手中的 NSO Group 间谍软件?内存错误。整个 2020 年代用于定向行动的那些 WebKit 零点击漏洞?内存错误。那个攻破了从 iPhone 5s 到 iPhone X 所有机型的 checkm8 越狱?内存错误。我几年前写过的 T2 漏洞 (https://ironpeak.be/blog/crouching-t2-hidden-danger/) 呢?同出一辙。Google 的 Project Zero 多年来一直在统计这些。在所有主要软件产品的高严重性漏洞中,大约**70%** 是内存安全错误。微软统计了他们的,得到了相同的数字。Chrome、Firefox、Apple 都做了统计。整个行业都同意:这就是问题所在。 那么,攻击者实际上能用内存错误做什么呢?最坏的情况下,他们想做什么都行。内核是运行所有其他程序的程序。如果你能破坏内核的内存,你就能重写那个写着“此进程允许访问你的照片”或“此用户 uid=501”的表格。你可以安装一些在重启后依然存在的东西。你可以读取该设备发送过的每一条消息。你可以在不点亮 LED 的情况下打开摄像头。你买来保护隐私的手机,现在成了别人记录你生活的日记。 这就是为什么 Apple、Microsoft、Google 以及芯片供应商在过去十年中,一直将工程资源投入到硬件级别的内存安全上。编译器加固、更安全的分配器、沙盒:它们都有帮助,但就像一个有漏洞的水桶。漏洞层出不穷。真正有效的方法是让即使存在的漏洞**无法利用**。不再信任软件是正确的;而是让硬件拒绝错误的操作。这就是 MIE 所做的赌注。 ### 回顾:MIE 应该做什么? 如果你在过去一年关注过 Apple 的安全营销,你可能会听到 MIE 被描述为“代际飞跃”。它由三个层次堆叠而成。 **内存标签 (EMTE)。** 内核分配的每一块内存都会获得一个隐藏的标签。你用来访问这块内存的指针,其高比特位中也嵌入了相同的标签,不会改变地址本身。每次访问时,CPU 都会检查:指针的标签是否与内存的标签匹配?如果不匹配,你的进程将立即终止。Apple 的版本是**同步的**,这意味着检查会在访问发生时立即触发,而不是稍后。你无法通过试探来猜测标签,因为第一次猜错就会导致进程死亡。 **只读区域。** 某些内核结构如此重要,以至于它们获得了额外的保护。比如 `ucred`(你进程的用户 ID)、Mach 任务控制块、沙盒表、代码签名状态。它们位于一个特殊区域,页表本身将这些页面标记为不可写。即使是 ring-0 的内核代码也无法触碰它们。硬件 MMU 会拒绝。 **唯一的入口。** 只有一个内核函数被允许改变只读区域中的页面:`_zalloc_ro_mut`。它会短暂地将一个页面标记为可写,执行写入,然后再次将其标记为不可写。一个名为安全页表监视器(Secure Page Table Monitor)的更高特权之物会监视每一次页表修改,并在任何其他人尝试时拒绝。从内核自身的角度来看,“只有 `_zalloc_ro_mut` 在此处写入”是不可打破的。 将这三者堆叠起来,你就得到了 MIE。大多数内核内存受到标签保护。核心数据则在页表层面受到保护,背后只有一个经过审计的写入者。老实说,这是个相当不错的设计。 ### 刚刚发生了什么? 在**2026 年 5 月 11 日**,Apple 发布了 macOS Tahoe 26.5。在安全说明 (https://support.apple.com/en-us/127115) 中,埋藏着一句话: > **Kernel。** 适用于配备 Apple Silicon 的 Mac 电脑。一个 App 或许能够导致系统意外终止。致谢:Calif.io(与 Claude 和 Anthropic Research 合作)。CVE-2026-28952。 “系统意外终止”听起来像一个崩溃漏洞。但并非如此。三天后,Calif 发布了他们的披露 (https://blog.calif.io/p/first-public-kernel-memory-corruption):第一个在启用 MIE 的 M5 上公开的 macOS 内核漏洞。无特权的本地用户,仅使用公开的系统调用,获取 root shell。从“手里没有漏洞”到可工作的利用程序,只用了五天。他们全程使用了 Anthropic 受限的 Mythos Preview 模型。 Apple 的补丁只有**两条指令**。就两条。这两条指令道出了整个故事。让我给你展示一下。 如果你能阅读 ARM64 汇编,以下就是整个修复的一览。我们将在下面详细解析。 ``
--- com.apple.kernel @ macOS 26.4.1 :: _zalloc_ro_mut, 边界检查
+++ com.apple.kernel @ macOS 26.5 :: _zalloc_ro_mut, 边界检查
@@ 参数验证 @@
- cmp x8, x29 ; 栈帧完整性检查(无用)
- b.lo skip_stack_check
- ; 6 条对齐掩码算术指令
- adds x9, x8, x4 ; 目标 + 长度,执行得太晚
- b.hs range_check
+ mrs x10, TPIDR_EL1 ; 每 CPU 指针
+ adds x9, x8, x4 ; 目标 + 长度,现在先执行
+ b.hs per_cpu_check
+ ldr x11, [x10, #0x158] ; 每 CPU 边界标记
+ ldr x10, [x10, #0xe8] ; 每 CPU 只读子区基址
+ cmp x8, x11
+ ccmp x9, x11, #0x0, hs ; 新增:目标+长度 vs 每 CPU 下界
+ ccmp x9, x10, #0x2, hs ; 新增:目标+长度 vs 每 CPU 上界
+ b.ls panic
`` 三个不同之处:一个无用的栈重叠检查被移除了,溢出检查被提前了,并且添加了一个全新的每 CPU 边界。如果这些你已经理解了,可以略读余下内容。如果没有,请继续阅读。 ### 一分钟看懂漏洞 整个问题发生在一个内核函数中:`_zalloc_ro_mut`。以下是它的 C 语言签名: ``
void _zalloc_ro_mut(zone_id_t zone, // 哪个只读区
void *target, // 要写入的槽位
size_t offset, // 槽内的偏移量
const void *src, // 要复制进来的字节
size_t len); // 字节数
`` 翻译一下:“在这个只读区中找到这个槽位,并将 `len` 个字节从 `src` 复制到 `target + offset`”。在执行任何操作之前,它必须进行边界检查,确保目标地址加上复制大小不会超出已分配空间的末尾。26.4.1 版本中的检查在伪代码中是这样的(为了清晰起见,我省略了 `offset` 参数;实际代码会先将其加到 `target` 上): ``
// 补丁前 (26.4.1)
uint64_t end = target + len;
// 等等...
if (end < target) { // 溢出检测
// 回绕处理路径,使用 `end`(已经回绕了!)
if (target >= ro_zone_lo && end <= ro_zone_hi) goto write_ok; // 🤡
}
`` 看出来了么?如果 `len` 大到使得 `target + len` 在 `2^64` 处回绕,那么 `end` 就变成了一个很小的数字。回绕处理路径**仍然**将这个很小的 `end` 与只读区范围进行比较。一个很小的数字显然低于 `ro_zone_hi`,因此检查通过。该函数愉快地使用*真实的* `len` 调用了 `memcpy(target, src, len)`,从而写入到经过验证的槽位之外,进入相邻的任何数据。 在只读区中,相邻的是什么?**其他 `ucred` 结构体。** 其他 `task_t` 块。AMFI 状态。代码签名标志。正是 MIE 旨在保护的那些东西。这就是 CVE-2026-28952。边界检查中的整数溢出。Apple 在 CVE 描述中说的“*通过改进的输入验证加以解决*”,现在终于说得通了。 ### 内存布局,因为图片有助于理解 快速建立心智模型。内核只是一个程序。像任何程序一样,它会分配内存并将内容存储在其中。将内核的内存想象成一个巨大的仓库,不同区域存放不同类型的东西。通用区域(大多数分配发生的地方)称为 **kalloc 堆**。为高价值结构锁定的区域是 **RO 区域**(只读区域)。画出来就是: ``
高地址
───────────────────────────────────────────────
│ │
┌─────────────────────────────────────────────┐
│ 内核代码和只读数据 │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 通用 kalloc 堆(EMTE 标签保护) │ ← 经典的
│ │ UAF/OOB
└─────────────────────────────────────────────┘ 漏洞落在这里
│ │
┌─────────────────────────────────────────────┐
│ 只读分配器区域(SPTM 锁定) │ ← SPTM 锁定
│ ┌─────────┬─────────┬─────────┬─────────┐│ 只读,
│ │ ucred │ task_t │ amfi │ ... ││ 只能由
│ └─────────┴─────────┴─────────┴─────────┘│ _zalloc_ro_mut
└─────────────────────────────────────────────┘ 写入
│ │
┌─────────────────────────────────────────────┐
│ 每 CPU 数据(TPIDR_EL1 指向这里) │
└─────────────────────────────────────────────┘
低地址
────────────────────────────────────────────────
`` 需要注意三件事: - **kalloc 堆**是内核分配几乎所有内容的地方。网络缓冲区、文件描述符、IPC 消息等等。经典的利用技术如 use-after-free 和越界写入通常发生在这里。 - **只读区域**是 Apple 选择在硬件级别锁定的部分。CPU 的页表将整个内核的这些页面标记为不可写。即使你完全攻破了内核并获得了一个任意写入原语,你仍然无法直接触碰这些页面。只有一个例外:`_zalloc_ro_mut`。 - 底部的**每 CPU 数据**保存着每个核心的簿记信息。ARM64 上的 `TPIDR_EL1` 寄存器指向它。记住这部分,它对于后续的修复很重要。 现在放大到只读区域中的一个槽位。我们关心的结构体是 `ucred`,即每个进程的“用户凭证”结构体。每当内核决定你的进程是否被允许做某事时,它都会检查 ucred。有趣的字段是 `cr_uid`,即你的有效用户 ID。`cr_uid == 0` 表示 root。`cr_uid == 501` 表示我。或者可能也是你。 相同类型的只读区域槽位紧密排列在一起,就像开放式办公室中的隔间。每个 `ucred` 大约 200 字节。以下是其中一个内部的样子(来自公开的 XNU 源码,`bsd/sys/ucred.h` (https://github.com/apple-oss-distributions/xnu)): ``
struct ucred {
os_refcnt_t cr_ref;
struct posix_cred {
uid_t cr_uid; // ← 有效 UID,目标奖品
uid_t cr_ruid; // ← 真实 UID
uid_t cr_svuid; // ← 保存的 UID
short cr_ngroups;
gid_t cr_groups[NGROUPS];
// ...
} cr_posix;
struct label *cr_label;
audit_token_t cr_audit;
// ...
};
`` 将单个 4 字节的 `cr_uid` 字段从 `501` 翻转为 `0`,你的进程就成为 root 了。仅此而已。无需代码注入,无需 ROP,无需 shellcode。内核在下一次系统调用时会愉快地接受,认为你就是结构体所说的那个人。 现在来看溢出本身,我会带着你一步步理解。该函数接受 `target`(要写入的位置)和 `len`(字节数)。在复制之前,它会计算 `target + len` 以检查写入不会超出已验证的槽位。但 ARM64 指针是 64 位宽的,64 位算术会**回绕**。如果 `target + len` 大于最大的 64 位数,结果就会回滚到一个很小的值。 用较小的数字举个简单的例子,以便直观理解。想象 8 位数学(数字 0 到 255)。如果 `target = 250` 且 `len = 10`,那么 `target + len = 260`。在 8 位世界中不存在 260;结果回绕为 `4`。一个天真的边界检查看到 `4`,说:“嗯,4 在数组范围内”,然后让写入继续进行。但**实际**写入是从 250 开始,一直写下去,穿过回绕,乱涂乱画超出末尾。64 位指针也做同样的事情,只不过数字大得多。 以下是漏洞中的实际值: ``
攻击者传入:
target = 0xfffffe00_abcd1000 ← 攻击者自身槽位内的地址
len = 0xffff_ffff_ffff_fe00 ← 巨大
target + len = 0xfffffe00_abcd0e00 ← 回绕了!位于 target 下方!
▲ │
补丁前检查使用这个 "end" 地址。因为它低于 target(而不是高于),所以它偷偷通过了 < ro_zone_hi 的比较。
检查通过 ✓
然后 memcpy 使用真实的 `len` 运行:
┌──── 已验证的槽位 ────┐
▼ ▼
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ 攻击者 │ 攻击者 │ 受害者 │ 受害者 │ 下一个 │
│ ucred │ ucred │ ucred │ ucred │ 槽位 │
└─────────┴─────────┴─────────┴─────────┴─────────┘
▲ ▲ ▲
└────── target ─┘ │
└──────── len 字节被复制 ────┘
攻击者字节渗入受害者 ucred → cr_uid = 0
`` 补丁前的检查看到了回绕后的 `target+len`(一个很小的数字,远低于只读区的上限),说了句“看起来没问题”,然后授权了写入。接着 `memcpy` 使用*真实的* `len`(巨大)运行。字节从已验证的槽位流出,进入了隔壁的任何内容。隔壁是什么?几乎肯定是另一个 `ucred`,因为相同类型的只读区槽位是相邻排列的。攻击者控制着被复制的字节(`src` 参数),所以他们选择一个负载,将四个字节的 `0x00000000` 精确放置在受害者 `cr_uid` 所在的偏移处。内核通过其自身可信的写入器写入这些字节。下次检查受害者进程的凭证时,该进程就是 root 了。攻击者安排自己成为那个进程,整个链条就完成了。敲响邪恶的笑声。 ### 两条指令的修复 我从我的研究机器上提取了 26.4.1 和 26.5 的内核缓存,并逐条指令对比了 `_zalloc_ro_mut`。该函数从 114 条指令变成了 116 条。三个实际变化: 1. **溢出检查被提前了。** 补丁前,`adds x9, x8, x4; b.hs` 序列在*无关的栈重叠检查之后*执行,因此回绕处理的后备路径在进入范围比较时带有一个已经回绕的值。补丁后,溢出检测是首先发生的事情。如果发生回绕,你就无处可去了。 2. **通过 `TPIDR_EL1` 寄存器**(ARM64 的每核心线程指针)添加了一个**每 CPU 边界**。补丁前,边界检查使用的是*全局*只读区范围。补丁后,目标地址还必须适合*当前 CPU 的*只读子区。内核为性能考虑维护了每个核心独立的只读子区,而旧代码没有强制执行这个边界。 3. **一个无用的栈重叠检查被移除了。** 一段将目标地址与帧指针进行比较的遗留代码。大部分情况下从未触发。消失了。 以下是实际的 diff。补丁前的 arm64e: ``
; macOS 26.4.1, _zalloc_ro_mut 位于 0xfffffe000b4e3560
cmp x8, x29 ; 栈帧完整性检查(无用)
b.lo skip_stack_check
; ... 6 条对齐掩码算术指令 ...
adds x9, x8, x4 ; 此处,太晚:target+len
b.hs range_check ; 溢出带着回绕的 x9 落入范围检查
`` 补丁后: ``
; macOS 26.5, _zalloc_ro_mut 位于 0xfffffe000b4e84d0
mrs x10, TPIDR_EL1 ; 每 CPU 指针
adds x9, x8, x4 ; 此处,首先:target+len
b.hs per_cpu_check
ldr x11, [x10, #0x158] ; 每 CPU 边界标记
ldr x10, [x10, #0xe8] ; 每 CPU 只读子区基址
sub x12, x
相似文章
@igus_ai: Apple 花费了5年时间构建保护其操作系统的防护措施。三名研究人员仅用6天就绕过了它,使用了…
三名安全研究人员使用 AI 模型 Claude Mythos 在六天内绕过了 Apple 的 MIE 保护,展示了一种罕见的利用方式,该方式无需传统恶意软件即可在 macOS 上获取 root 权限。完整论文将在 Apple 发布补丁后公布。
首个针对Apple M5的macOS内核内存损坏漏洞公开
来自加州的研究人员披露了首个针对Apple M5的macOS内核内存损坏漏洞,该漏洞绕过了MIE硬件缓解措施,并在最新版macOS上实现了本地权限提升。
首个公开的Apple M5 macOS内核内存损坏漏洞利用在Mythos Preview的帮助下5天内完成
加州研究人员借助AI工具Mythos Preview,在Apple M5硬件上构建了首个公开的macOS内核内存损坏漏洞利用,绕过了MIE。该利用链耗时5天,将在苹果修复漏洞后完全披露。
@cryptopunk7213: claude mythos刚刚攻破了苹果价值20亿美元的防御系统。它通过发现一个完全不同的攻击向量……
Claude Mythos AI发现了一种新型攻击向量,在五天内以3.5万美元的成本绕过了苹果M5芯片的防御系统,并生成一份55页的报告交付给苹果。该漏洞通过污染芯片摄入的数据,规避了苹果的MIE系统。
Anthropic 的 Mythos 刚刚帮助发现了一个可能突破苹果安全防护的 macOS 漏洞
Anthropic 的 Mythos AI 模型帮助网络安全公司 Calif 发现了两个先前未记录的 macOS 漏洞,这些漏洞可能绕过苹果的内存完整性保护机制,展示了该模型在通过 Project Glasswing 进行受控访问下的攻击能力。