原文链接: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 存在多项缺点:
- 接口复杂,需要手动解析二进制 audit 记录;
- 审计事件不包含 Code Signing 信息,需额外查询;
- 是被动的(事件发生后才上报),难以实现主动防御。
Apple Endpoint Security Framework
随着苹果禁止三方进入内核并推出 System Extensions,苹果在 10.15 引入了新的 Endpoint Security Framework (ESF),它提供用户态接口以订阅安全事件。
优势:
- 清晰简单的 API;
- 事件中直接包含 Code Signing 信息;
- 可主动响应(拦截)或被动监听安全事件。
环境与前提
要使用 Endpoint Security Framework 需要:
- Entitlement:
com.apple.developer.endpoint-security.client- 向 Apple 申请或在禁用 SIP 环境下自行添加:
<key>com.apple.developer.endpoint-security.client</key> <true/>
- 向 Apple 申请或在禁用 SIP 环境下自行添加:
- Xcode 11 及 macOS 10.15 SDK
- 系统版本:macOS 10.15 及以上
创建 ES 客户端
- 头文件位置:
/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include/EndpointSecurity/ - 在代码中:
#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); - 返回值类型
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_id、team_id、cdhash→ 签名信息; -
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_count 和 es_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,项目中只需:
- 引入头文件
ProcessMonitor.h; - 链接
libbsm和libEndpointSecurity; - 添加 ES Entitlement;
- 实例化并启动监控:
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 极大简化了实现流程:
- 调用
es_new_client与es_subscribe建立客户端并订阅事件; - 监听
ES_EVENT_TYPE_NOTIFY_EXEC/FORK/EXIT即可捕获进程创建与终止; - 从
es_process_t解析 PID、路径、参数及签名信息。
该框架使得用户态安全工具开发更为高效与安全。
原文与示例代码版权归 Patrick Wardle 与 Objective-See 所有,译文仅供学习参考。