构建 Endpoint Security 应用
Build an Endpoint Security app
2020年6月24日
一句话判断
Endpoint Security Framework 是 macOS 上做企业安全管控的官方武器——如果你还在用 KEXT 做安全监控,Apple 已经明确告诉你:这条路走到了尽头。
这场 Session 讲了什么
Endpoint Security (ES) Framework 是 Apple 在 macOS 10.15 中引入、在 macOS Big Sur 中大幅增强的系统安全框架。它替代了传统的 Kernel Extension (KEXT) 方式来做端点安全监控。Session 系统讲解了 ES 框架的架构、订阅机制和事件处理流程。
ES 框架的核心是事件订阅模型。你的安全应用通过 ESNewSubscriber 注册到系统中,声明自己关心的系统事件类型(如进程创建、文件写入、网络连接等)。当这些事件发生时,系统会将事件详情异步推送给你的应用,你可以选择允许(AUTH)或拒绝(DENY)该操作。整个架构运行在用户空间,不需要加载内核扩展,这意味着更好的系统稳定性和更低的崩溃风险。
Session 还介绍了 macOS Big Sur 中的新能力:支持更多的事件类型、更丰富的元数据(如代码签名信息、进程树关系)、以及性能优化。特别值得一提的是新的 es_event_type_t 枚举扩展到了 60+ 种事件类型,几乎覆盖了所有安全相关的系统行为。
值得深挖的点
Auth 模式与 Notify 模式的区别
ES 框架提供两种监听模式。Auth 模式允许你在事件发生前进行拦截决策——你的回调返回 ES_AUTH_RESULT_ALLOW 或 ES_AUTH_RESULT_DENY,系统会据此放行或阻止操作。Notify 模式则只是事后通知,不参与决策。Session 建议:只对真正需要拦截的事件使用 Auth 模式,其他都用 Notify 模式,因为 Auth 模式会增加系统延迟。
Mute 路径的性能优化
ES 事件的吞吐量可能非常惊人——一个繁忙的 macOS 系统每秒可能产生数万个文件操作事件。Session 介绍了 es_mute_path 和 es_mute_process API,可以过滤掉不需要监控的路径和进程。比如你可以 mute /System/Library/ 路径,只关注用户空间和第三方应用的行为。这个优化直接决定了你的安全应用会不会成为性能瓶颈。
代码片段
import EndpointSecurity
import Foundation
// 创建 Endpoint Security 客户端
class EndpointSecurityMonitor {
var client: OpaquePointer?
func startMonitoring() -> Bool {
// 创建 ES 客户端,需要系统扩展权限
var client: OpaquePointer?
let result = es_new_client(&client) { client, message in
// 事件回调处理
Self.handleMessage(message, client: client)
}
guard result == ES_NEW_CLIENT_RESULT_SUCCESS else {
print("创建 ES 客户端失败: \(result)")
return false
}
self.client = client
// 订阅关心的事件类型
var events: [es_event_type_t] = [
ES_EVENT_TYPE_AUTH_EXEC, // 进程执行
ES_EVENT_TYPE_AUTH_OPEN, // 文件打开
ES_EVENT_TYPE_AUTH_KILL, // 进程终止
ES_EVENT_TYPE_NOTIFY_CONNECT, // 网络连接
]
let subscribeResult = es_subscribe(
client!,
&events,
UInt32(events.count)
)
return subscribeResult == ES_RETURN_SUCCESS
}
static func handleMessage(_ message: es_message_t,
client: OpaquePointer) {
let eventType = message.event_type
switch eventType {
case ES_EVENT_TYPE_AUTH_EXEC:
handleExecEvent(message)
case ES_EVENT_TYPE_AUTH_OPEN:
handleFileOpenEvent(message)
default:
break
}
}
}
// 处理进程执行事件并做出授权决策
extension EndpointSecurityMonitor {
static func handleExecEvent(_ message: es_message_t) {
guard let execEvent = message.event.exec else { return }
// 获取被执行的文件路径
let targetPath = String(cString: execEvent.target.executable.path.data)
// 获取父进程信息
let parentPid = message.process.audit_token.pid
// 安全策略:阻止从 /tmp 目录直接执行
if targetPath.hasPrefix("/tmp/") {
print("阻止从临时目录执行: \(targetPath), 父进程: \(parentPid)")
// 返回拒绝决策(Auth 模式)
es_respond_auth_result(
client!,
&message,
ES_AUTH_RESULT_DENY,
false
)
return
}
// 检查代码签名信息
let signingInfo = message.process.codesigning_flags
// 验证签名的完整性...
// 允许执行
es_respond_auth_result(
client!,
&message,
ES_AUTH_RESULT_ALLOW,
false
)
}
}
// 使用 Mute 路径优化性能
extension EndpointSecurityMonitor {
func setupMuteRules(client: OpaquePointer) {
// 过滤掉系统目录,减少不必要的事件
var systemPaths = [
"/System/Library/",
"/usr/lib/",
"/Library/SystemExtensions/",
]
for path in systemPaths {
let esPath = es_string_token_t(
length: UInt32(path.utf8.count),
data: path
)
es_mute_path(client, esPath, ES_MUTE_PATH_TYPE_PREFIX)
}
// Mute 特定进程(如系统守护进程)
let systemProcesses = [
"com.apple.kernel",
"com.apple.securityd",
]
for proc in systemProcesses {
// 按进程标识符 mute
// 减少来自系统核心进程的事件噪音
}
print("Mute 规则配置完成")
}
}
最佳实践
- ES 客户端必须在 System Extension 中运行,不能直接在普通 app 进程中使用——需要通过
systemextensionsctl安装 - Auth 回调中绝对不能做阻塞操作(如网络请求、文件 I/O),否则会拖慢整个系统;需要做异步决策时使用缓存和本地规则
- 在生产环境中同时部署 Notify 模式的日志收集和 Auth 模式的实时防护,前者用于事后分析,后者用于实时拦截
- 为 ES 客户端配置合理的缓存大小(
es_cache_size),过小会导致事件丢失,过大会占用过多内存 - 做好降级策略——如果 ES 客户端崩溃,系统会在短暂延迟后移除你的订阅并自动放行所有被阻塞的操作
还有什么值得关注
- Session 提到 ES 框架的 Audit Token 包含了丰富的进程属性(PID、UID、GID、signing ID 等),比传统 KEXT 拿到的信息更全面
- macOS Big Sur 引入了
es_event_type_t的大量新增事件类型,包括 mount/unmount、signal 发送、IPC 通信等 - 对于企业级部署,ES 应用需要通过
MDM(Mobile Device Management) 推送安装,不能通过 App Store 分发