3、数据采集层:Windows事件日志采集、Linux系统日志采集、MacOS日志采集

数据采集层,是整个EDR系统的「眼睛」和「耳朵」。说白了,没有这一层,后面所有的分析、检测、溯源都是空中楼阁。我做了这么多年安全,见过太多厂商把精力全花在检测算法上,结果底层日志没采全——嗯,那真是白搭。

这一章,我们就把三大主流操作系统的日志采集机制掰开揉碎。Windows、Linux、MacOS,一个都不能少。

3.1 Windows事件日志采集

Windows的日志体系,我个人认为是三大系统里最成熟的。从Windows Vista开始引入的ETW(Event Tracing for Windows)框架,到现在依然是核心。

核心要点:Windows日志采集主要依赖两个通道——Event Log(传统事件日志)和ETW(事件追踪)。前者适合事后审计,后者适合实时监控。

3.1.1 事件日志通道分类

Windows把日志分成了几个大类,我习惯用一张表来记:

通道名称 日志文件路径 典型事件
System %SystemRoot%\System32\winevt\Logs\System.evtx 服务启动/停止、驱动加载
Security %SystemRoot%\System32\winevt\Logs\Security.evtx 登录成功/失败、权限变更
Application %SystemRoot%\System32\winevt\Logs\Application.evtx 应用程序错误、安装事件
Sysmon %SystemRoot%\System32\winevt\Logs\Microsoft-Windows-Sysmon.evtx 进程创建、网络连接、文件变更

这里我要特别提一下Sysmon。它虽然不是Windows原生组件,但在我参与过的所有EDR项目中,Sysmon几乎是必装的。为什么?因为它能补上原生日志的短板——比如进程命令行、网络连接详情、文件创建时间戳,这些原生日志要么没有,要么不全。

我的经验:如果你只采Security日志,很多横向移动的痕迹根本看不到。加上Sysmon的Event ID 3(网络连接)和Event ID 1(进程创建),检测覆盖率能提升至少40%。

3.1.2 采集方式与代码示例

Windows日志采集,主流方式有两种:Windows Event Log API和ETW Consumer。我个人更推荐后者,因为ETW是内核级的事件源,延迟更低。

下面是一个用C#通过EventLog API读取Security日志的示例:

using System;
using System.Diagnostics.Eventing.Reader;

class WindowsLogCollector
{
    static void Main()
    {
        string logQuery = "*[System[(EventID=4624)]]"; // 只查登录成功事件
        EventLogQuery query = new EventLogQuery("Security", PathType.LogName, logQuery);
        EventLogReader reader = new EventLogReader(query);

        using (reader)
        {
            for (EventRecord eventInstance = reader.ReadEvent();
                 eventInstance != null;
                 eventInstance = reader.ReadEvent())
            {
                Console.WriteLine("Event ID: {0}", eventInstance.Id);
                Console.WriteLine("Time: {0}", eventInstance.TimeCreated);
                Console.WriteLine("Message: {0}", eventInstance.FormatDescription());
                Console.WriteLine("---");
            }
        }
    }
}

这段代码看起来简单,但实际生产环境里,你还要处理日志轮转、权限提升、缓冲区溢出等问题。我曾经在一个客户现场遇到过——Security日志文件被锁住,因为Windows自身的日志服务正在写入,我们的采集进程直接读就报错。后来加了文件共享模式的重试机制才解决。

避坑指南:千万不要用轮询方式去读.evtx文件。Windows的日志文件在写入时是独占锁的,你直接读会报「文件被占用」。正确做法是用EventLogReader API或者ETW订阅。

3.2 Linux系统日志采集

Linux的日志体系,说实话,比Windows要「散」一些。没有统一的ETW框架,全靠各个组件自己写日志。但好处是——一切皆文件,采集起来反而更灵活。

3.2.1 核心日志源

Linux下,我主要关注这几个日志源:

  • syslog/rsyslog: 传统日志守护进程,大部分应用日志都走这里。配置文件在 /etc/rsyslog.conf。
  • journald: systemd 自带的日志系统,二进制格式存储,用 journalctl 查看。路径在 /var/log/journal/。
  • auditd: Linux审计框架,能记录系统调用级别的行为。比如谁执行了execve、谁打开了文件。
  • 应用日志: 比如 /var/log/nginx/access.log、/var/log/mysql/error.log。

你想想看,如果只采syslog,很多内核级的行为根本抓不到。比如一个进程通过execve执行了恶意脚本,syslog里可能只有一条模糊的记录,但auditd能告诉你完整的命令行、父进程PID、用户ID。

3.2.2 采集实战:auditd + journalctl

我个人习惯的做法是:用auditd做系统调用级别的监控,用journald做应用日志的聚合。下面是一个auditd规则的例子:

# 监控所有用户的execve系统调用
-a always,exit -F arch=b64 -S execve -k process_execution

# 监控文件删除操作(重点关注 /etc 和 /tmp)
-w /etc -p wa -k etc_changes
-w /tmp -p wa -k tmp_changes

# 监控网络连接(connect系统调用)
-a always,exit -F arch=b64 -S connect -k network_connect

配置好之后,重启auditd服务,日志会写入 /var/log/audit/audit.log。然后你可以用 ausearch 来查询:

ausearch -k process_execution --start today | aureport -f

嗯,这里要注意。auditd的日志量非常大。如果你不加过滤地全量采集,一个中等规模的服务器一天能产生几个GB的日志。我建议只监控关键的系统调用,比如execve、open、connect、bind,其他的可以酌情关闭。

核心原则:Linux日志采集,宁可漏采也不要过度采集。过度采集会导致磁盘IO飙升,影响业务性能。我曾经见过一个案例,某EDR厂商在Linux服务器上开了全量auditd监控,结果数据库服务器的TPS直接掉了30%。

3.3 MacOS日志采集

MacOS的日志体系,其实和Linux有相似之处,但也有自己的特色。它从OS X 10.10开始引入了统一的日志系统——unified logging。说白了,就是把所有日志都集中到一个地方,用 log 命令来管理。

3.3.1 unified logging 的核心概念

MacOS的日志系统有几个关键点:

  • 日志存储: 二进制格式,存储在 /var/db/diagnostics/ 和 /var/db/uuidtext/ 下。你不能直接用cat看,必须用 log 命令。
  • 日志级别: Default、Info、Debug、Error、Fault。默认情况下,只有Error和Fault级别的日志会被持久化。
  • 子系统与类别: 每个应用可以注册自己的子系统(如 com.apple.security),然后按类别(如 auth、network)输出日志。

我刚开始做MacOS采集时,踩过一个坑——默认情况下,Info级别的日志是不保存的。这意味着很多应用的行为日志,你根本看不到。后来我加了配置,把日志级别调低到Info,才把那些「隐形」的行为抓出来。

3.3.2 采集命令与代码示例

用 log 命令实时采集:

# 实时采集所有日志(输出到标准输出)
log stream --level debug --predicate 'subsystem == "com.apple.security"'

# 采集过去一小时的日志
log show --last 1h --predicate 'eventMessage contains "ssh"' --info

如果你要用代码采集,MacOS提供了OSLog框架。下面是一个Swift示例:

import OSLog

let logStore = try OSLogStore.local()
let predicate = NSPredicate(format: "subsystem == %@", "com.apple.security")
let entries = try logStore.getEntries(matching: predicate)

for entry in entries {
    if let message = entry.composedMessage {
        print("\(entry.date): \(message)")
    }
}

这段代码看起来干净,但实际部署时要注意权限问题。MacOS从Catalina开始,对日志访问做了严格的沙盒限制。你的采集进程必须要有「com.apple.private.logging.store」权限,否则什么都读不到。

避坑指南:MacOS的unified logging在日志轮转上非常激进。系统会自动删除超过7天的日志,而且没有配置选项可以延长。如果你需要长期保存,必须自己写一个定时任务把日志导出到外部存储。

3.4 跨平台采集架构图

下面我用一张SVG图来总结三大平台的采集架构。这张图是我自己画项目方案时常用的结构,你可以直接拿去用。

EDR数据采集层:跨平台日志采集架构 Windows Linux MacOS 采集源 Event Log (evtx) ETW (Event Tracing) Sysmon 采集源 syslog/rsyslog journald auditd 采集源 unified logging log stream OSLog框架 EDR Agent 采集代理(跨平台统一接口) 数据输出:JSON / Protobuf → 消息队列

这张图的核心逻辑是:底层不管是什么操作系统,EDR Agent都要提供一个统一的采集接口。我在实际项目中,通常会把采集到的日志统一转成JSON或Protobuf格式,然后推送到Kafka或RabbitMQ。这样上层分析模块就不用关心底层是Windows还是Linux了。

我的建议:跨平台采集时,一定要统一时间戳格式。Windows用FILETIME,Linux用epoch,MacOS用mach_absolute_time。如果不做归一化,后面做事件关联时你会疯掉。我一般统一转成UTC毫秒时间戳。

好了,数据采集层就讲到这里。三大平台的日志各有各的脾气,但只要掌握了核心机制,采集起来并不复杂。下一层我们就要进入数据处理和归一化环节了——那才是真正考验架构设计能力的地方。


专注资料整理