3. 终端行为数据模型

EDR 的检测能力并不来自“采集了多少条日志”,而来自“能否把一次终端行为还原成一条可追溯的因果链”。进程、文件、网络、注册表、驱动、WMI 这六类事件,分别记录了行为的不同侧面:进程回答“谁在执行”,文件回答“改动了什么”,网络回答“与谁通信”,注册表回答“持久化配置被如何修改”,驱动回答“内核层是否被加载”,WMI 回答“是否通过管理接口执行了命令或订阅了事件”。数据模型的任务,就是给这些侧面定义统一的结构,并用稳定的关联键把它们串起来。

一、统一事件信封:让六类事件可被同一套逻辑处理

工程上通常先定义一个公共事件信封(Event Envelope),所有类型的事件都继承它。信封字段不描述行为语义,只描述“这条事件是谁、在何时、在哪台机器上产生的”,从而让后续关联、检索、聚合有统一入口。一个可落地的信封结构如下:

{
  "event_id": "01HZX8...",          // 全局唯一,ULID 或 UUIDv7
  "ts": 1718000000123,              // 事件发生时间,毫秒,UTC
  "host_id": "host-7f3a",           // 终端稳定标识
  "boot_id": "boot-20240610-01",    // 本次开机会话标识
  "pid": 4820,                      // 主体进程 PID
  "ppid": 1204,                     // 父进程 PID
  "proc_guid": "p-9c1e...",         // 进程实例唯一标识(关键)
  "user": "CORP\\alice",
  "integrity": "Medium",            // 完整性级别
  "event_type": "file_write",
  "payload": { ... }                // 各类型专属字段
}

其中 proc_guid 是整个模型的枢纽。PID 会被操作系统复用,仅靠 PID 关联会把不同进程的行为错误地合并;而 proc_guid 在进程创建时生成、随进程退出而失效,能唯一标识一个进程实例。父进程同样应记录 parent_proc_guid,这样即使父进程已退出,仍能沿 GUID 回溯调用链。

二、六类事件的专属字段设计

进程事件关注创建与退出。创建事件需记录 image_path、command_line、image_hash(SHA256)、signer、parent_proc_guid、session_id。命令行是检测中最有价值的字段之一,但需注意其可能包含敏感参数,采集侧应做脱敏策略配置。

文件事件关注写入、重命名、删除。核心字段为 file_path、operation、size、file_hash、entropy(可选,用于识别加密行为)。路径应统一为规范化绝对路径,避免 ..\ 与短名(8.3 格式)造成同一文件多种表示。

网络事件关注连接建立。字段包括 proto、src_ip、src_port、dst_ip、dst_port、domain(若可解析)、direction。其中 direction 需明确定义参照系:本文统一以“终端为观察点”,出站为 outbound、入站为 inbound。

注册表事件关注键值修改。字段为 key_path、value_name、old_data、new_data、operation。Run 键、服务键、IFEO、COM 劫持点等是持久化的高频位置,模型上可用 key_category 做语义标注,便于规则直接引用。

驱动事件关注内核模块加载。字段为 driver_path、driver_hash、signer、load_result、service_name。驱动事件数量少但价值极高,通常应默认全量采集。

WMI 事件关注两类行为:一是 Win32_Process.Create 等方法的调用(远程或本地执行),二是 __EventFilter / CommandLineEventConsumer 的持久化订阅。字段为 namespace、class_name、method、query、consumer。

三、关联关系建模:从孤立事件到行为图

六类事件通过三类关系连接:主体关系(事件由哪个进程实例产生,用 proc_guid)、因果关系(父子进程、进程与它创建的文件/连接/注册表项)、时序关系(同一 boot_id 内按 ts 排序)。下图给出一次“可疑下载并持久化”行为的关联结构,可以看到同一 proc_guid 如何把文件、网络、注册表事件聚合到一条链上。

终端行为事件关联模型(示例:下载并持久化) explorer.exe proc_guid: p-1001 powershell.exe proc_guid: p-9c1e parent_proc_guid 网络连接 dst: 203.0.113.45:443 direction: outbound 文件写入 C:\Users\alice\a.exe hash: 3f8a...c21 注册表修改 ...\Run\Updater new_data: a.exe proc_guid proc_guid proc_guid 驱动加载 driver: xyz.sys signer: 未签名 WMI 订阅 __EventFilter + Consumer namespace: root\subscription 时序关联 时序关联 t0 进程创建 t1 网络连接 t2 文件写入 t3 注册表修改 t4 驱动/WMI 关联键: proc_guid(主体) · parent_proc_guid(因果) · boot_id + ts(时序) · host_id(终端) 检测价值: 单条事件仅描述动作,链式关联才能识别“下载→落地→持久化”的完整攻击行为

图中实线表示由 proc_guid 直接建立的主体关联,虚线表示跨进程的时序关联(例如驱动加载与 WMI 订阅可能由不同进程触发,但发生在同一时间窗口内)。底部时间轴说明:单看 t2 的文件写入只是普通写文件,只有把它与 t1 的网络连接、t3 的注册表修改放在同一 proc_guid 下,才能形成有检测意义的判断。

四、建模时的三个工程要点

第一,关联键必须稳定且不可复用。 proc_guid 应在进程创建时由采集端生成并写入所有后续事件;若依赖 PID,进程退出后 PID 被复用会导致关联错乱。同理,host_id 应基于硬件或安装时生成的持久标识,而非主机名(主机名可被修改)。

第二,时间戳要统一时基。 六类事件可能来自不同采集通道(用户态钩子、内核回调、ETW),若各自使用本地时间,跨通道关联会出现毫秒级偏差。建议统一使用 UTC 毫秒时间戳,并在信封中保留 ts_mono(单调时钟)用于同一终端内的严格排序。

第三,字段语义要可被规则直接引用。 例如注册表事件的 key_category 若预先标注为 run_key、service_key、ifeo,检测规则就不必写复杂的路径匹配;网络事件的 direction 若统一以终端为参照,规则中的“出站连接”判断就不会因采集点不同而歧义。模型设计阶段多花时间统一语义,检测规则阶段就能少写大量特判逻辑。

实践建议:先用一份 JSON Schema 固化事件信封与六类 payload 的字段约束,再让采集端、传输管道、检测引擎共用同一份 Schema。这样新增事件类型时,只需扩展 payload 定义,关联逻辑与检索入口无需改动。

把六类事件放进统一信封、用 proc_guid 等稳定键建立关联、并统一时间与字段语义,终端行为数据才从“一堆日志”变成“一张可查询、可追溯、可检测的行为图”。后续的检测规则、威胁狩猎与事件回溯,都建立在这张图之上。