从一次电脑卡顿到 CVE-2025-24118:一次 macOS 内核漏洞的黑盒复现分析
本文记录了一次在 macOS Sequoia 环境下,从一次偶然的系统卡顿与“不应该成功的文件操作”切入,尝试对 CVE-2025-24118(XNU 内核 credential 竞态条件)进行黑盒压测与排查的完整心路历程。受限于黑盒环境与内核时序的极高不确定性,本文重点分享多线程并发模型的构建思路与踩坑反思,欢迎各位师傅共同探讨。
记一次围绕 CVE-2025-24118 的 macOS 内核竞态模型黑盒压测与排查复盘
CVE-2025-24118 漏洞概述
- 漏洞编号:CVE-2025-24118
- 漏洞描述:XNU 内核在处理进程凭证(credential)时存在竞态条件,可能允许本地攻击者绕过系统完整性保护(SIP),在开启 SIP 的情况下对受保护的系统文件(如 /usr/bin 等)进行非授权修改,进而实现本地权限提升。
- 受影响版本:macOS Sequoia 15.2 及更早版本(其他大版本的影响范围未完全确认,但 Sequoia 为主线受影响范围)
- 修复版本:macOS Sequoia 15.3
- 漏洞类型:竞态条件 → SIP 绕过 → 本地权限提升
- 危害程度:高危
实验环境
电脑:MacBook Pro M1(2021)
前言
很多漏洞研究并不是从一个明确的漏洞入口开始。
有时候,一个“不应该出现的现象”,才是真正的突破口。
这次对 CVE-2025-24118 的复现经历,就是从一次普通的电脑卡顿开始的。
由于日常学习、开发以及安全研究需求,我的电脑中长期运行着大量环境:
IntelliJ IDEA,PyCharm,以及多个虚拟机环境,还有各种测试项目。
同时,由于早期留下的大量实验项目没有及时清理,许多废弃环境长期占用磁盘空间和内存资源。随着时间推移,系统开始出现明显异常:
频繁提示内存不足,应用响应速度下降,开发环境出现大量卡顿。
于是我开始清理旧项目和无用环境。
然而,就是在这个过程中,一个完全意料之外的现象出现了。
1.一个不应该成功的文件操作
macOS 中存在一项重要的安全机制:
System Integrity Protection(SIP)
SIP 的目标是保护系统关键组件,避免恶意程序或者误操作修改系统核心文件。
例如:
/System
/usr/bin
/bin
/sbin这些路径默认受到 SIP 保护。
正常情况下,即使拥有 root 权限,只要 SIP 处于开启状态:
csrutil status显示:
System Integrity Protection status: enabled也无法随意修改这些系统文件。
但是,在一次环境清理过程中,我发现了一个异常现象:
没有在mac恢复界面执行:
csrutil disable
关闭 SIP 的情况下,我依然可以对:
/usr/bin
目录下的文件进行:移动,删除等操作
这个行为明显不符合正常情况下 macOS 的安全模型。
问题随之产生:
为什么一个按照设计应该被阻止的操作,最终成功执行了?
2.初步排查
发现异常后,我首先考虑的是:
是不是环境配置导致?
1.检查 SIP 状态
首先确认 SIP 是否真的开启:
csrutil status
结果:
System Integrity Protection status: enabled
排除:
SIP 被主动关闭或者配置错误。
2.排查权限因素
进一步测试发现:
这个问题并不是简单的 root 权限导致。
因为:
root 权限并不等于可以绕过 SIP。
SIP 本身就是为了限制 root 对系统关键区域的修改。
当我更换测试环境后,依然能够观察到类似异常。
因此问题变成:
系统的安全边界是否在某些情况下出现了异常?
3.黑盒分析:从异常现象寻找规律
由于当时没有源码分析条件,也没有现成 PoC。
因此只能从行为入手
我开始记录:例如触发条件,操作流程,系统状态以便于我可以进行稳定复现。
在实验中逐渐发现:这个现象并不是固定出现。
但是在以下情况下更容易出现:
- 系统资源压力较高
- 存在大量文件操作
- 后台任务较多
- 系统响应明显延迟。
这让我产生了一个猜测:
是否存在某种竞态条件(Race Condition),导致系统状态出现不同步?
4.关于 CVE-2025-24118 的漏洞模型
需要说明:
前面观察到的 /usr/bin 文件操作异常,是黑盒复现过程中发现的现象
而结合公开分析后,该漏洞与CVE-2025-24118极为相似,但是CVE-2025-24118 的核心问题并不是简单的 SIP 文件路径检查绕过。
其真正漏洞点位于:
XNU 内核 credential(进程权限凭证)管理过程中的竞态条件
在 macOS 内核中,每个进程都有对应的 credential 信息
如图
credential 保存了进程身份以及权限相关信息,如果出现问题,可能会导致:
权限状态异常,内核对象状态混乱,内核内存破坏等问题
5.模拟 credential 竞态模型
下面代码并不是 macOS XNU 真实源码,这里我使用伪代码来构造一个简化模型,用来演示竞态条件如何影响安全状态
假设:
struct credential
{
int uid;
int gid;
};
struct process
{
struct credential *cred;
};正常情况下:
void update_credential(struct process *p)
{
struct credential *new;
new = allocate();
new->uid = 0;
new->gid = 0;
p->cred = new;
}流程为:
创建新的credential -> 替换进程 credential -> 释放旧对象
但是,如果多个线程同时操作:
线程A:
void replace_credential()
{
process->cred = new_cred;
free(old_cred);
}线程B:
void check_permission()
{
cred = process->cred;
check(cred->uid);
}可能会出现:
线程 A:释放旧 credential ->线程 B:仍然访问旧对象
直接导致
访问错误对象 ->状态混乱->内核安全问题
这就是典型 Race Condition问题
6.为什么系统压力可能影响触发条件
这里我采取保守思路:
不能简单的就规划为 内存不足就一定导致漏洞产生。
更加合理的推测是:极端资源压力改变了系统运行时序。
正常情况下:Thread A - Thread B 两个操作间隔非常短。
但是高负载情况下:
时间窗口可能扩大。原本难以触发的竞态条件,可能更容易出现
因此资源压力更像是:触发环境+辅助条件,而不是漏洞根因
七、 压力测试方案设计:Python + C 多线程联合压测
为了模拟并验证这种时序冲突,早期的单线程 Shell 脚本或简单的 Python 内存打满方案存在致命缺陷:Shell 命令(cp/mv)开销过大,且 Python 容易因瞬间触发 OOM 被系统秒杀(Kill: 9),根本无法精准撞击内核微观窗口。
为此,我设计了一套“Python 动态内存波浪压测 + C 语言多线程高频冲击”的组合方案。
1. 宏观压测:Python 动态内存波浪脚本
避免直接将系统压死,采用“分配 -> 保持 -> 主动释放清理”的波浪式循环,制造内核资源回收的动态窗口
#!/usr/bin/env python3
import time
import sys
CHUNK_SIZE_MB = 100
# 针对 16GB 内存机器,设为 12000 (12GB);如果是 8GB,设为 6000
MAX_SAFE_MB = 12000
chunks = []
print("=== macOS 暴力内存压力测试启动 ===")
print(f"目标:压榨至 {MAX_SAFE_MB} MB,强制触发系统内存压力。按 Ctrl+C 停止。")
try:
while True:
# 每次申请 100MB,并强制填充数据(防止系统惰性分配)
block = bytearray(b'A' * (CHUNK_SIZE_MB * 1024 * 1024))
chunks.append(block)
total_mb = len(chunks) * CHUNK_SIZE_MB
print(f"[+] 强占内存中... 当前实际占用: {total_mb} MB (约 {total_mb / 1024:.2f} GB)")
# 稍微给一点时间让系统渲染任务管理器
time.sleep(0.1)
# 触及上限时释放
if total_mb >= MAX_SAFE_MB:
print(f"[*] 达到高压阈值,主动释放内存...")
del chunks[:len(chunks) // 2]
print(f"[-] 释放后剩余: {len(chunks) * CHUNK_SIZE_MB} MB\n")
time.sleep(2)
except KeyboardInterrupt:
print("\n[!] 停止测试,释放内存。")
chunks.clear()2. 微观冲击:C 语言高并发冲击程序
使用 pthread 编写原生多线程,绕过解释器开销,以每秒数百万次的频率高频并发冲击目标系统路径,并捕捉返回值。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#define THREAD_COUNT 4
#define TARGET_PATH "/usr/bin/test_race_temp"
#define SOURCE_PATH "/tmp/test_demo"
volatile int go_flag = 0;
void* file_race_worker(void* arg) {
while (!go_flag) {
__asm__ volatile("yield");
}
// 高频并发冲击目标操作
for (int i = 0; i < 50000; i++) {
int ret = rename(SOURCE_PATH, TARGET_PATH);
if (ret == 0) {
printf("\n[+] [!!!] 捕获异常:rename 成功返回 0!SIP 拦截被绕过!\n");
}
unlink(TARGET_PATH);
}
return NULL;
}
int main() {
int fd = open(SOURCE_PATH, O_CREAT | O_RDWR, 0644);
if (fd != -1) {
write(fd, "race_data", 9);
close(fd);
}
pthread_t threads[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_create(&threads[i], NULL, file_race_worker, NULL);
}
usleep(10000);
go_flag = 1; // 瞬间触发多线程并发流
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_join(threads[i], NULL);
}
printf("[*] 测试批次结束。\n");
return 0;
}编译指令
clang -O3 sip_race.c -o sip_race -lpthread8.总结
在实际复现过程中,我观察到了几个非常有趣的现象:
- 活动监视器的滞后窗口:当系统进入极端资源压力时,活动监视器出现了明显的滞后(约 9 秒的延迟响应),这说明用户态感知到的状态与内核真实调度之间存在时间差。
- 极端的边界状态:在早期的偶然成功中,系统伴随着严重的卡顿甚至险些崩溃。这表明,这类涉及内核 credential 管理的竞态条件,往往极度依赖“极致的系统资源波动”来拉长时序窗口。
复盘结论: 虽然在标准多线程压测脚本下,受限于极低的触发概率,未能稳定复现最初那次伴随系统崩溃边缘的“奇迹”,但整个黑盒推导与多线程压测模型的构建过程,充分展示了 macOS 内核在高压状态下的行为表现。希望本文的排查思路能为后续研究内核竞态的师傅们提供一些参考与启发。