Sinter:macOS 用户模式安全强制新方案

Sinter 是首个完全用 Swift 编写的开源 macOS 端点保护代理,支持 Apple 的 EndpointSecurity API。本文详细解析其架构设计、实时决策机制、TOCTOU 风险缓解策略,以及如何通过 System Extension 提升系统防护能力。

Sinter:macOS 用户模式安全强制新方案

TL;DR:Sinter 是首个完全用 Swift 编写的开源端点保护代理,支持 Apple 全新的 EndpointSecurity API。它展示了如何构建成功的事件授权安全代理,并解决了所有端点保护代理在 macOS 11 Big Sur 发布前从内核模式迁移到用户模式时将面临的诸多挑战。

简单、开源且基于 Swift

Sinter 是我们为 macOS 10.15 及以上版本开发的新开源端点安全强制代理,完全使用 Swift 编写。我们从零开始构建它,作为 100% 用户模式代理,利用新的 EndpointSecurity API 从 macOS 内核接收一组安全相关事件类型的授权回调。Sinter 通过简单规则控制事件允许或拒绝,且不使用传统反病毒解决方案中昂贵的全系统扫描或基于签名的检测。

立即获取测试版安装程序并试用!

目前,Sinter 允许您编写一组规则来阻止或允许进程执行事件,并通过与 Santa 兼容的同步服务器或本地配置文件向代理提供规则(这是一个演示显式允许的示例规则)。然而,我们计划开发更复杂的规则语法,并为 API 支持的许多其他类型事件添加阻止能力,这也将意味着 Santa 规则兼容性的结束。

追求 100% 用户模式安全代理

实现端点安全解决方案(例如,反病毒、反恶意软件)需要实时拦截和授权 OS 级别事件。历史上,这意味着使用内核模式回调 API 或在未提供适当 API 时挂钩内核模式操作系统代码。操作系统开发人员早就知道,像这样的第三方内核模式代码是系统不稳定和不安全的主要来源,因为内核代码中的任何小错误往往都会产生严重后果。

于是 macOS EndpointSecurity API 应运而生。2019 年底,Apple 宣布在 macOS 中弃用对所有第三方内核扩展的支持,并将引入用户模式 API 和框架来替代第三方产品所需的功能。所有安全供应商都接到通知:在明年内弃用您现有的内核模式解决方案,并在下一个 macOS 版本(macOS 11 Big Sur)发布前迁移到 EndpointSecurity API。对许多团队来说,这显然不是一个有趣的前景,公告发布后不久,一位客户就委托我们开发一个用户模式解决方案,以使迁移不那么痛苦。

什么是 EndpointSecurity API?

EndpointSecurity 是一个 API,它在特定事件即将发生时实时实现从 macOS 内核的回调。EndpointSecurity 客户端订阅一种或多种事件类型,这些类型要么是 NOTIFY 类型,要么是 AUTH(授权)类型。Notify 顾名思义,对于在主机上捕获简单活动日志非常有用。授权回调更强大;它允许客户端进程决定允许或拒绝事件发生。

EndpointSecurity 取代了 macOS 上实时事件授权的内核模式等效项(Kauth KPI 和其他不受支持的内核方法)以及只读事件监控 OpenBSM 审计跟踪。任何用于 macOS 的实时监控或保护产品都必须重写以使用 EndpointSecurity,以便在 macOS 11 Big Sur 上运行。

请注意,EndpointSecurity API 中没有网络相关事件(除了 UNIX 域套接字)。所有这些都在 Network Extension 框架中。您可以从一个系统扩展中结合使用这两个 API,但这里我们特别关注 EndpointSecurity API。

使用此 API,FireEye 的 Stephen Davis 和 Objective-See 的 Patrick Wardle 迅速发布了事件监控应用程序,例如可以实时显示与进程相关和与文件相关的事件。但是,追随 Process Monitor(“ProcMon”)脚步的只读监控工具虽然有用,但仅使用了 EndpointSecurity API 功能的一半(监控能力)。Google 的 Santa 是一个用 Objective-C 编写的开源 macOS 进程允许/拒绝解决方案,展示了使用 EndpointSecurity 授权事件的能力:其代理现在接收来自 EndpointSecurity 的进程事件并做出允许/拒绝决策。

我们看到掌握 EndpointSecurity API 至关重要,因为许多团队需要在其现有的 macOS 安全应用程序中迁移到它。通过 Sinter 的开发,我们深入研究了 EndpointSecurity,从经验中吸取了一些教训,并为我们遇到的各种挑战提供了解决方案——这样您就不必再经历这些。Sinter 还展示了用 Swift 编程语言实现 EndpointSecurity 客户端,它承诺比 Objective-C 具有更好的内存安全性和性能,同时保持与所有其他新 macOS API 的兼容性。

开发 Sinter:非胆小者所能为

实现事件授权代理比实现只读事件订阅者困难一个数量级。我们还通过艰难的方式了解了 EndpointSecurity API 的某些缺点。以下是我们在 Sinter 开发过程中所做的一些更重要的繁重工作。

1. 实时决策而不影响系统

实现安全事件授权代理可能最困难的部分是授权决策必须实时做出。您不能永远阻塞以做出决策,EndpointSecurity 对每个授权消息强制执行截止时间:如果您的客户端超过截止时间,EndpointSecurity 将终止您的客户端进程以保持系统正常运行。

决策不应同步做出;Sinter 使用 es_copy_message 从 EndpointSecurity 中出队消息,并允许其调度程序立即向您发送下一条消息。决策应在单独的线程中做出,并尽可能从每个线程异步响应。有些决策处理时间会比其他决策长,但通常执行签名检查所需的 API 无法中断。

使用 Sinter 时,当涉及大型程序的快速爆发执行事件导致 Sinter 锁定机器时,我们正面遇到了这个问题。我们通过实现一个高效的队列系统解决了这个问题,一个队列用于小程序,另一个队列用于大程序,这样事件就永远不会在队列中等待而卡住。大程序队列在进程外工作,因此可以在必要时中止长时间运行的验证。这种新方法在我们所有的测试中都表现可靠。

2. 缓解实时安全决策中的 TOCTOU 风险

TOCTOU(检查时间,使用时间)竞态条件漏洞模式在做出安全决策时常见。任何执行检查的安全代理都不允许在检查和操作批准之间的时间内修改已检查的资源。

当授权 macOS 执行事件时,被检查的资源是可执行文件,它在执行前被映射到内存中。这是一个 TOCTOU 攻击场景:

恶意行为者执行 Bad.app。坏的可执行文件被映射到内存中,EndpointSecurity 发出执行授权事件。但随后攻击者立即替换或修改可执行文件以使其成为 Good.app。EndpointSecurity 客户端获取事件,验证捆绑包及其文件看起来都良好,并允许执行。

这个问题并非 EndpointSecurity 独有,并且始终是之前 KAuth 框架的风险(例如,不久前在 Santa 中提出了关于此 TOCTOU 的问题)。对于任何想要授权事件的代理来说,这仍然是一个必须解决的挑战。如前所述,Sinter 尝试监控文件事件以捕获 TOCTOU 攻击。如果 Apple 在 EndpointSecurity API 本身内部处理此责任,那将会容易得多(已作为开发者反馈建议 FB8352031 提交给 Apple;参见 OpenRadar)。

3. macOS 可执行文件存在于应用程序捆绑包中

执行事件发生在单个可执行文件的上下文中,但大多数 macOS 可执行文件存在于应用程序捆绑包中,这是在 macOS Finder 中显示为单个“.app”文件的目录状结构。捆绑包本身是代码签名的,代码签名验证必须在捆绑包级别完成。这意味着捕获执行事件的安全代理必须发现可执行文件是否包含应用程序捆绑包,然后验证整个捆绑包上的代码签名——这些不是由 EndpointSecurity 本身执行的任务。像 Apple 的 Xcode.app 这样的一些捆绑包大小高达千兆字节,实时处理验证是不可能的。执行事件必须一开始就被拒绝,直到验证完成。

EndpointSecurity 确实提供了一个内置缓存机制,一个由所有 EndpointSecurity 客户端共享的单一缓存。但是,作为客户端,您不能使此缓存中的单个条目无效;您只能一次清除整个缓存。如果相关文件被更改/删除等,EndpointSecurity 将自动使缓存项无效,但这是基于每个文件进行的,而不是基于每个应用程序捆绑包。目前,Sinter 使用两个缓存:一个由 EndpointSecurity 管理,另一个包含应用程序捆绑包代码签名验证结果的自定义缓存。

理论上,恶意软件可以被添加到应用程序捆绑包中,如果捆绑包中先前批准的可执行文件没有更改,EndpointSecurity 不会通过清除缓存的批准决策来做出反应。EndpointSecurity 客户端必须自行监控此情况,并响应地使整个缓存无效。这并不理想,我们希望 Apple 会改进此缓存机制。在短期内,EndpointSecurity 客户端可能必须对应用程序捆绑包实施自己的完整性监控,以避免以这种方式被绕过。Sinter 尝试自己的捆绑包文件完整性监控能力,以检测何时应清除此自定义缓存。

4. 将代理安装为系统扩展的优势

“系统扩展”是 Apple 对“扩展系统的用户模式组件”的称呼,并且是取代现已弃用的第三方内核扩展的总称。EndpointSecurity 是此总称下的一个 API;DriverKit 和 Network Extensions 是另外两个。系统扩展也是一种新型的 macOS 托管插件包,您可以通过它安装可执行文件。

不需要将 EndpointSecurity 客户端安装为系统扩展——您可以从任何类型的可执行文件(甚至是基本命令行应用程序)实现所有 EndpointSecurity 功能——但强烈鼓励这样做。当代理安装为系统扩展时,有额外的好处和系统强制保护。系统扩展可以选择在启动时在所有其他第三方应用程序之前加载。Apple 还宣布 macOS 将 SIP(系统完整性保护)扩展到覆盖系统扩展,这意味着它甚至阻止 root 用户卸载您的安全代理。历史上,这只有在您开发自己的内核模式防篡改逻辑时才可能,但将代理安装为系统扩展使您免于重新发明此轮子。Sinter 目前是一个后台守护进程,但现在 Apple 已经记录了将代理安装为系统扩展的防篡改保护好处,我们将把 Sinter 转换为此格式。

5. 掌握权利、签名和公证工作流程

EndpointSecurity API 仅可在由 Apple 批准的开发人员(如 Trail of Bits)进行代码签名和公证的应用程序中使用。换句话说,该 API 由特殊权利门控。与大多数权利不同,此权利需要手动申请和 Apple 批准,之后您将获得带有 EndpointSecurity 权利的代码签名证书。在我们的案例中,批准时间是六个日历周,但您的情况可能有所不同。Apple 显然对此权利非常谨慎,因为行为不当或恶意的 EndpointSecurity 客户端可能会使主机系统上的所有内容停止。

Apple 的代码签名和公证步骤在失败时难以排除故障,因此尽早设置和自动化流程至关重要,这样您将在它们中断时立即注意到并轻松缩小中断更改的范围。对于 Sinter,我们创建了自己的 CMake 驱动方法,自动化了 Apple 的公证、打包、包签名和包公证步骤的工作流程。所有这些现在都完美集成到我们的 CI 中,几乎无需麻烦。

EndpointSecurity 代理需要的最后一个权利与用户隐私有关。因为大多数代理将检查文件(无论是在文件事件的上下文中还是在进程事件的可执行文件中),它们需要用户的许可来访问文件系统。在您的应用程序首次运行时或之前,用户必须手动转到系统偏好设置中的隐私设置,并启用“完全磁盘访问”。有 MDM 有效负载可以自动启用权限并绕过此手动用户批准步骤。

这些是我们在编写 Sinter 时解决的更棘手挑战,当然还有更多杂项陷阱和经验教训(例如,确定文件是否为二进制文件、签名验证和多个 EndpointSecurity 客户端)。随着开发的继续,我们将更新最引人注目的细节——请保持关注。

结果

随着内核扩展的弃用,Apple 正在为端点保护代理创造公平的竞争环境:每个人都必须使用相同的用户模式 API。这将通过改进系统稳定性和减少攻击面使每个人受益,但现有的安全产品开发人员首先必须用用户模式方法替换其内核扩展。在用户模式下,他们现在可以使用任何语言工作,而不仅仅是 C/C++。

因此,与其仅从 C 示例代码从头开始,我们希望组织能帮助我们构建并依赖 Swift 中的开源平台,这是 Apple 继 Objective-C 之后的长期投资的未来选择。

参与 Sinter

Sinter 的测试版现已可用。这是重要的第一步,以下是我们目前正在处理的一些较大项目的预览:

  • 扩展阻止规则的标准,在规则语法中添加更多灵活性:问题 4, 17, 24, 25
  • 基于 EndpointSecurity 中提供的文件事件实现强大的文件完整性监控能力
  • 类似地,通过检查来自 EndpointSecurity 的 mmap 和相关事件来防止内存中代码注入攻击
  • 合并 NetworkExtension 框架,以监控和授权网络事件,如网络流和 DNS 请求

我们邀请您与我们合作赞助 Sinter 的持续开发,或讨论将基于 EndpointSecurity 的功能集成到您现有的代理中——只需联系我们开始。

也欢迎贡献者!在 GitHub 上给我们反馈,或加入 Empire Hacking Slack 上的 #sinter 频道。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计