Logo
Overview

转载:使用Apple的Endpoint Security框架开发进程监视器

November 4, 2025

原文链接:Objective-See Blog – Writing a Process Monitor with Apple’s Endpoint Security Framework


写在前面

安全工具的常见组件之一是进程监视器(process monitor)——它监听新进程的创建事件并提取相关信息,如 PID、路径、参数及 Code Signing 信息。

Patrick Wardle 在其多款 Objective-See 工具中都使用了进程监控逻辑,例如:

  • RansomWhere?:监控加密行为,判断是否为勒索软件。
  • TaskExplorer:实时显示系统运行的进程。
  • BlockBlock:关联持久化事件与具体进程。

由于每次都需重复编写监控逻辑,他后来将其封装为独立库 ProcInfo(基于 OpenBSM)。


从 OpenBSM 到 Endpoint Security Framework

在 macOS 10.15 以前,用户态想捕获进程事件主要依赖 OpenBSM 审计子系统

但 OpenBSM 存在多项缺点:

  1. 接口复杂,需要手动解析二进制 audit 记录;
  2. 审计事件不包含 Code Signing 信息,需额外查询;
  3. 是被动的(事件发生后才上报),难以实现主动防御。

Apple Endpoint Security Framework

随着苹果禁止三方进入内核并推出 System Extensions,苹果在 10.15 引入了新的 Endpoint Security Framework (ESF),它提供用户态接口以订阅安全事件。

优势:

  • 清晰简单的 API;
  • 事件中直接包含 Code Signing 信息;
  • 可主动响应(拦截)或被动监听安全事件。

环境与前提

要使用 Endpoint Security Framework 需要:

  1. Entitlement: com.apple.developer.endpoint-security.client
    • 向 Apple 申请或在禁用 SIP 环境下自行添加:
      <key>com.apple.developer.endpoint-security.client</key>
      <true/>
  2. Xcode 11 及 macOS 10.15 SDK
  3. 系统版本:macOS 10.15 及以上

创建 ES 客户端

  1. 头文件位置:
    /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include/EndpointSecurity/
  2. 在代码中:
    #import <EndpointSecurity/EndpointSecurity.h>
    es_client_t *endpointClient = nil;
    result = es_new_client(&endpointClient, ^(es_client_t *client, const es_message_t *message) {
        // 事件处理
    });
    if (result != ES_NEW_CLIENT_RESULT_SUCCESS)
        NSLog(@"es_new_client failed: %d", result);
  3. 返回值类型 es_new_client_result_t 表示初始化结果(成功、权限不足等)。

编译时需链接 libEndpointSecurity 库。


订阅事件

es_event_type_t events[] = {
    ES_EVENT_TYPE_NOTIFY_EXEC,
    ES_EVENT_TYPE_NOTIFY_FORK,
    ES_EVENT_TYPE_NOTIFY_EXIT
};
if (ES_RETURN_SUCCESS != es_subscribe(endpointClient, events, sizeof(events)/sizeof(events[0])))
    NSLog(@"es_subscribe failed");
  • ES_EVENT_TYPE_AUTH_* 事件需响应才能继续(可拦截)。
  • ES_EVENT_TYPE_NOTIFY_* 仅通知。

    本例只使用 NOTIFY_* 型事件来被动监听。


事件回调与消息结构

回调接收 es_message_t *message,其中包含:

  • process:触发事件的进程;
  • event_type:事件类型;
  • event:事件数据联合体。

根据事件类型提取目标进程:

es_process_t *process = NULL;
switch (message->event_type) {
case ES_EVENT_TYPE_NOTIFY_EXEC:
    process = message->event.exec.target; break;
case ES_EVENT_TYPE_NOTIFY_FORK:
    process = message->event.fork.child; break;
case ES_EVENT_TYPE_NOTIFY_EXIT:
    process = message->process; break;
}

解析 es_process_t

结构体包含:

  • audit_token → 可用 audit_token_to_pid() 取 PID;
  • executable->path → 进程路径;
  • signing_idteam_idcdhash → 签名信息;
  • codesigning_flags → 签名标志位。

路径类型为 es_string_token_t (长度 + 指针),可转为 NSString:

NSString *s = [NSString stringWithUTF8String:
    [[NSData dataWithBytes:stringToken->data length:stringToken->length] bytes]];

提取进程参数

ES_EVENT_TYPE_NOTIFY_EXEC 事件中,参数位于 message->event.exec.args 中,需用 es_exec_arg_countes_exec_arg 函数解析:

for (uint32_t i = 0; i < es_exec_arg_count(&event->exec); i++) {
    es_string_token_t arg = es_exec_arg(&event->exec, i);
    NSString *argument = convertStringToken(&arg);
    [self.arguments addObject:argument];
}

提取签名信息

self.signingInfo[@"flags"] = @(process->codesigning_flags);
self.signingInfo[@"signing_id"] = convertStringToken(&process->signing_id);
self.signingInfo[@"team_id"] = convertStringToken(&process->team_id);
self.signingInfo[@"is_platform_binary"] = @(process->is_platform_binary);
NSMutableString *cdHash = [NSMutableString string];
for (int i=0; i<CS_CDHASH_LEN; i++) [cdHash appendFormat:@"%X", process->cdhash[i]];
self.signingInfo[@"cdhash"] = cdHash;

处理退出事件

ES_EVENT_TYPE_NOTIFY_EXIT 事件结构为:

typedef struct {
    int stat;
    uint8_t reserved[64];
} es_event_exit_t;

可直接取 message->event.exit.stat 作为退出码。


Process Monitor 库封装

Wardle 将上述逻辑封装成开源库 libProcessMonitor.a,项目中只需:

  1. 引入头文件 ProcessMonitor.h
  2. 链接 libbsmlibEndpointSecurity
  3. 添加 ES Entitlement;
  4. 实例化并启动监控:
ProcessMonitor *procMon = [[ProcessMonitor alloc] init];
ProcessCallbackBlock block = ^(Process *process) {
    switch (process.event) {
        case ES_EVENT_TYPE_NOTIFY_EXEC: NSLog(@"EXEC"); break;
        case ES_EVENT_TYPE_NOTIFY_FORK: NSLog(@"FORK"); break;
        case ES_EVENT_TYPE_NOTIFY_EXIT: NSLog(@"EXIT"); break;
    }
    NSLog(@"%@", process);
};
[procMon start:block];
[[NSRunLoop currentRunLoop] run];

输出示例:

PROCESS EXEC ('ES_EVENT_TYPE_NOTIFY_EXEC')
pid: 7655
path: /bin/ls
uid: 501
args: (ls, "-lart", ".")
signing info: { cdHash = 5180A360C9..., signatureIdentifier = "com.apple.ls" }

PROCESS EXIT ('ES_EVENT_TYPE_NOTIFY_EXIT')
pid: 7655
exit code: 0

总结

在 macOS 10.15 以前,编写用户态进程监视器复杂且局限。

新的 Endpoint Security Framework 极大简化了实现流程:

  1. 调用 es_new_clientes_subscribe 建立客户端并订阅事件;
  2. 监听 ES_EVENT_TYPE_NOTIFY_EXEC/FORK/EXIT 即可捕获进程创建与终止;
  3. es_process_t 解析 PID、路径、参数及签名信息。

该框架使得用户态安全工具开发更为高效与安全。


原文与示例代码版权归 Patrick Wardle 与 Objective-See 所有,译文仅供学习参考。

comment

留言 / 评论

如果暂时没有看到评论,请点击下方按钮重新加载。