一、概述
事件日志是 Windows 唯一由系统主动、持续、按审计规则记录的证据。它和注册表、Prefetch 这类「顺手留下的痕迹」有本质区别:注册表与 Prefetch 是副产品(为配置和加速而写,可能被清理,也可能因功能未启用而不存在),事件日志则是专职记录——存在的唯一目的就是记录「谁在什么时候做了什么」。
这个区别决定了取证分工:事件日志给出「行为」(开了什么服务、登录失败几次、程序被启动),注册表与 Prefetch 只给出「状态」(配置了什么、缓存里有什么)。在「某人在某时做了什么」这个问题上,它是 Windows 上最强的单一来源。
但它也是最容易被针对性清理的工件,因为攻击者清楚它记录什么。两条硬规则必须先立在脑子里:
1102(Security 日志被清除)本身是反取证的最强证据——日志被清却仍留下 1102 记录,说明清理者没清干净。
- 日志缺失不等于无活动。 必须先确认日志是否存在、被谁清过、上限多大,再谈「没发现什么」。
要处理的对象是 .evtx 的结构、怎么把它导出成可分析数据,以及那些必须逐条记住的事件 ID。
这条线在流程里的位置是「工件已提取、开始按时间线组织证据」,几乎所有 Windows 时间线都从这里起头。上游依赖三件事:活系统导出要走 wevtutil epl(不能直接拷贝正在被写入的文件);日志配置与保留策略必须先查(这决定了「能查到什么」);Sysmon 若已部署则审计能力显著更高,见Sysmon 与 Windows 日志聚合。动作顺序固定:先查 FileSize 与 MaximumSizeInBytes 判断是否已发生覆盖,再确认哪些通道存在、是否被清过,最后才导出解析。下游支撑整个时间线——事件日志带年份、带时区、精度到毫秒,是 Windows 侧少数高精度时间源之一,它为其它工件(Prefetch、SRUM 的小时粒度)提供锚点。
.evtx 是分块 + 变长记录结构,不能像 XP 时代的 .evt(16 KB 定长)那样按固定偏移读:
| 组成 |
说明 |
| 文件头 |
魔数 ElfFile\0、校验和、日志块大小(通常 4096)、chunk 数量 |
| Chunk(数据块) |
每块通常 4096 字节,存放已写满的事件数据区 |
| String table |
各事件的用户名、对象名等变长字符串集中存放,记录里只存偏移 |
| BinXML 片段 |
事件数据体通常以 BinXML 编码,描述字段名、类型和值 |
| 整体布局 |
ElfFile\0 → 头 → [Chunk0]…[ChunkN] → [String Table][BinXML] → 尾部校验 |
| 取值方式 |
** ToXml()** |
| 落点 |
%SystemRoot%\System32\winevt\Logs\*.evtx,按通道分文件 |
| 配置判据 |
FileSize ≈ MaximumSizeInBytes 表示已写满、正在循环覆盖;LogMode = Circular 为默认 |
三条关键推论:不能字节偏移定位第 N 条事件(必须顺序解析块与记录链表);字符串表是分离的(部分工具字段空/乱码往往源于字符串表未完整提取);文件尾部有校验(截断或损坏的 .evtx 用 wevtutil 打开可能直接报损坏)。
这也是为什么不该用文本编辑器或 hexdump 直接看 .evtx。
据此能回答的是:登录成功与失败的具体记录(含账号、来源、登录类型)、服务与驱动的安装与启动、策略变更、计划任务创建、用户与组变更、审计策略本身的变更、以及日志被清除的时刻与操作者。
回答不了的部分有六条,最前面三条是最常踩的实操错误:
- 取事件数据不能写死
Properties[N]。 这是最容易踩的实操要点,几乎每个新手都会栽——必须用 ToXml()。Properties[N] 的字段顺序随事件类型与版本变化,写死索引会取到错位的值,而错误的数据比没有数据更危险。
- 已覆盖的时段无法判断。 事件日志是固定大小循环覆盖的,满了就覆盖最旧的;
Security.evtx / System.evtx / Application.evtx 默认上限均为 20 MB,高频环境下几小时就可能覆盖掉数天前的记录,Sysmon 日志覆盖更快。一旦被覆盖,磁盘上不会留下任何痕迹——这是「日志缺失」最常见的技术原因,而非人为清理。委托方问「3 个月前有没有人登录」而日志只有 4 MB 几百条记录时,正确回答是「该时段已被系统自动覆盖,无法判断」。
.evt 与 .evtx 的解析方式不能通用。 16 KB 定长与分块变长是两种结构,用错解析器只会得到垃圾或「文件损坏」的错误结论。
- 字段空或乱码不代表数据缺失。 多数情况是字符串表未被完整提取,需要换工具或换解析路径。
- 非审计策略覆盖的行为。 日志只记录策略要求记录的,策略没开的行为不会出现在任何日志里。
- Sysmon 特有事件的含义。 若系统未部署 Sysmon,就不存在这些事件,「没找到」是预期状态。
往下读之前先确认你已经理解 .evtx 的分块与 BinXML 结构(这是判断「文件损坏」还是「解析器不对」的前提)、Windows 审计策略的语义(事件 ID 的意义依赖策略上下文)、时间戳的纪元与时区处理(事件日志的时间精度是毫秒级且带时区信息),并且会用 PowerShell 的 Get-WinEvent 与 wevtutil,活系统导出与离线解析走的是不同命令。还有一点决定结论强度:FileSize 与 MaximumSizeInBytes 的关系必须在分析前确认,否则所有否定性结论都是无根据的。
二、核心原理
2.1 .evtx 的结构:为什么不能像 .evt 那样按固定偏移读
XP 时代的 .evt 是 16 KB 定长记录——第 N 条事件就在第 N × 16384 字节。Win7 引入的 .evtx 换成了分块 + 变长记录,这条捷径直接失效:
| 组成 |
说明 |
| 文件头 |
魔数 ElfFile\0、校验和、日志块大小(通常 4096)、chunk 数量 |
| Chunk(数据块) |
每块通常 4096 字节,存放已写满的事件数据区 |
| String table |
各事件的用户名、对象名等变长字符串集中存放,记录里只存偏移 |
| BinXML 片段 |
事件数据体通常以 BinXML 编码,描述字段名、类型和值 |
ElfFile\0 → 头 → [Chunk0][Chunk1]…[ChunkN] → [String Table][BinXML 片段] → 尾部校验
三条关键推论:不能字节偏移定位第 N 条事件(必须顺序解析块与记录链表);字符串表是分离的(部分工具字段空/乱码往往源于字符串表未完整提取);文件尾部有校验(截断或损坏的 .evtx 用 wevtutil 打开可能直接报损坏)。
这也是为什么不该用文本编辑器或 hexdump 直接看 .evtx:它是二进制 + BinXML,hexdump 只能看到零散可读字符串。必须用能解析 BinXML 的工具。
2.2 ★ 取事件数据必须用 ToXml(),不要写死 Properties[N]
这是本篇最重要的实操要点,几乎每个新手都会踩。
# ✗ 错误:下标随事件 ID/版本错位;Message 依赖系统语言与消息资源,离线常为空
$e.Properties[8].Value
$e.Message
# ✓ 正确:ToXml() 后按字段名取
$e = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624} -MaxEvents 1
[xml]$x = $e.ToXml()
$x.Event.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' } | Select-Object -ExpandProperty '#text'
$x.Event.EventData.Data | ForEach-Object { "$($_.Name) = $($_.'#text')" } # 列出全部字段
$x.System.EventID; $x.System.TimeCreated.SystemTime # 事件元信息
$x.System.Execution.ProcessID # 产生该事件的进程 ID
Properties[N] 不可靠的四个原因:
| 原因 |
说明 |
| 字段数随事件 ID/版本变化 |
同一个 4624,不同版本/场景下 EventData 字段数不同,下标会错位 |
Message 受语言影响 |
它需要「消息资源 DLL」,离线环境常缺失,可能返回空或本地化文本 |
| 字段名被丢弃 |
Properties 只给值不给名字,出了错无从判断 |
| 改一次版本就错 |
Windows 升级后事件模板微调,下标全部平移 |
离线场景(从镜像提取的 .evtx):同样按 EventData 字段名取值——EvtxECmd 之类工具输出的键值 CSV 正是这一思路的离线实现,这才是可引用的证据格式。
2.3 关键事件 ID 清单
下面这份清单是 Windows 取证最实用的部分之一,建议单独打印。
A. 登录 / 账户(Security.evtx)
| ID |
含义 |
取证意义 |
| 4624 |
登录成功 |
核心事件。LogonType 区分类型(见 3.4 对照表) |
| 4625 |
登录失败 |
暴力破解/凭据填充信号。看 TargetUserName + IpAddress + 频率 |
| 4648 |
用显式凭据登录 |
LogonType = 3,典型为 runas 提权,高频恶意指标 |
| 4672 |
特殊权限分配 |
管理员登录的确认 |
| 4634 / 4647 |
注销 / 主动注销 |
会话结束时间 |
| 4720 / 4722 / 4724 / 4726 |
创建 / 启用 / 重置密码 / 删除账户 |
破坏性账号操作 |
| 4728 / 4732 / 4756 |
加入安全组(管理员/本地/全局) |
提权到管理员的关键事件 |
| 4776 |
NTLM 认证尝试 |
网络认证;DC 侧另有 4777 |
| 4768 / 4769 / 4771 |
Kerberos TGT / 服务票据 / 预认证失败 |
域环境认证分析 |
B. 服务(System.evtx)
| ID |
含义 |
取证意义 |
| 7045 |
安装了新服务 |
持久化最强指标之一。对应注册表 Services 新键 |
| 7034 / 7036 |
关键服务意外终止 / 状态变更 |
服务崩溃、被杀、生命周期 |
| 7000 / 7001 |
服务启动失败 |
配置损坏或被篡改 |
| 7040 / 4697 |
服务启动类型被修改 |
禁用或延迟启动的篡改 |
C. 日志与审计(反取证必查)
| ID |
通道 |
含义 |
取证意义 |
| 1102 |
Security |
Security 日志被清除 |
★ 反取证最强指标 |
| 104 |
System |
事件日志被清除(EventLog 服务) |
旧系统(XP/2003)对应的清除事件 |
| 4719 |
Security |
系统审计策略被修改 |
有人改了审计规则,通常是为规避记录 |
| 4616 |
System |
系统时间被更改 |
时间戳完整性受损,影响全案时间线可信度 |
D. 系统与应用
| ID |
通道 |
含义 |
取证意义 |
| 6005 / 6006 / 6008 |
System |
日志启动 / 正常关机 / 异常关机 |
6008 = 非正常关机(断电/强制重启) |
| 12 / 13 |
System |
操作系统启动 / 关闭 |
启动与关闭时间 |
| 20001 / 20003 |
System |
USB 设备安装 / 首次连接 |
外接设备接入 |
| 4104 / 4103 |
PowerShell |
脚本块记录 / 模块记录 |
命令行执行(需事先启用审计) |
| 1 / 2 / 3 |
Sysmon |
进程创建 / 网络连接 / 注册表等 |
见 04 |
★ 6008 的价值常被低估:它直接告诉你「这台机器上次是异常关机」,从而决定当前会话的 ShimCache、内存态工件是否可信(见 06)。每次做 Windows 取证都应先查 6008。
2.4 日志配置与保留策略决定「能查到什么」
事件日志是固定大小循环覆盖的,满了就覆盖最旧的。Security.evtx / System.evtx / Application.evtx 默认上限均为 20 MB(可用组策略放大),高频环境下几小时就可能覆盖掉数天前的记录;Sysmon 日志若不主动调大上限,覆盖速度更快。
一旦被覆盖,磁盘上不会留下任何痕迹。 这是「日志缺失」最常见的技术原因,而非人为清理。
# 查当前大小、最大大小与记录数(判断是否可能已覆盖)
Get-WinEvent -ListLog * |
Select-Object LogName, RecordCount, FileSize, MaximumSizeInBytes, LogMode |
Where-Object { $_.RecordCount -gt 0 } | Sort-Object FileSize -Descending
| 判断 |
含义 |
FileSize ≈ MaximumSizeInBytes |
日志已写满,正在循环覆盖——更早事件已被冲掉 |
LogMode = Circular |
循环覆盖(默认);Retain 才是保留模式 |
实战意义:委托方问「3 个月前有没有人登录」,而 Security 日志只有 4 MB、记录数只有几百条时,正确回答是「该时段已被系统自动覆盖,无法判断」,而不是「没发现登录记录」。
三、操作步骤
3.1 第 1 步:提取事件日志
# 列出所有 .evtx(Win10/11 常见路径 /Windows/System32/winevt/Logs)
# Sysmon 日志文件名含 %4 编码(Microsoft-Windows-Sysmon%4Operational.evtx),勿漏
fls -o 2048 -r -p /work/DigiForensics.dd | grep -iE '\.evtx$' > /evidence/evtx-list.txt
# 批量提取:inode 在第 2 列,落到同名文件
fls -o 2048 -r -p /work/DigiForensics.dd | grep -iE '\.evtx$' | grep -E '^r/r' \
| awk '{print $2}' | while read -r ino; do
name=$(echo "$ino" | sed 's/.*-//')
icat -o 2048 /work/DigiForensics.dd "$ino" > "/evidence/logs/$name.evtx"
done
验证:wevtutil 能正常打开且不报损坏,EvtxECmd 能解析出记录数。
3.2 第 2 步:活系统上导出(wevtutil epl)
wevtutil epl 把日志导出为 XML,是归档和跨机器转移的标准手段:
wevtutil el # 列出所有日志
wevtutil gl Security # 查看某日志配置
wevtutil epl Security C:\evidence\Security.xml -lf:true -sq # 真实格式、静默
wevtutil epl System Application C:\evidence\logs.xml -lf:true -sq
# 只导出指定时间范围(XPath 过滤,注意用的是 UTC)
wevtutil epl Security C:\evidence\
ov.xml -lf:true `
-q:"*[System[TimeCreated[@SystemTime>='2024-11-01T00:00:00.000Z' and @SystemTime<='2024-11-16T00:00:00.000Z']]]"
⚠️ 导出的是当时快照,不含被覆盖的历史;XML 不便统计,做时间线前应转成 CSV 等结构化格式。
3.3 第 3 步:解析为结构化数据(推荐 EvtxECmd)
EvtxECmd.exe -f C:\evidence\Security.evtx --csv C:\evidence\evtx_out # 单文件
EvtxECmd.exe -d C:\evidence\logs --csv C:\evidence\evtx_out # 递归整目录
EvtxECmd.exe -d C:\evidence\logs --csv C:\evidence\out --offset +8 # 本地时间 → UTC
EvtxECmd.exe -d C:\evidence\logs --csv C:\evidence\out --maps .\Maps # 自定义 maps
关键 CSV 列:TimeCreated(事件创建时间,按 --offset 换算)、Id / Provider / Channel / Computer(事件 ID、日志来源、通道、主机名),以及按 EventData 字段名成列的各列——这正是 ToXml() 思路的离线实现。
CSV 便于 Import-Csv 统计,且不依赖分析机语言。
3.4 第 4 步:活系统用 Get-WinEvent 精准查询
# 4.1 取关键字段:ToXml + 按字段名,FilterHashtable 走服务端过滤(快)
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624; StartTime=(Get-Date '2024-11-15')} |
ForEach-Object {
[xml]$x = $_.ToXml(); $d=@{}; $x.Event.EventData.Data | ForEach-Object { $d[$_.Name]=$_.'#text' }
[pscustomobject]@{ Time=$_.TimeCreated; User=$d['TargetUserName'];
LogonType=$d['LogonType']; SourceIP=$d['IpAddress']; Process=$d['ProcessName'] }
} | Format-Table -AutoSize
# 4.2 暴力破解检测:4625 按「用户+IP」聚合
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625} -MaxEvents 5000 |
ForEach-Object {
[xml]$x = $_.ToXml(); $d=@{}; $x.Event.EventData.Data | ForEach-Object { $d[$_.Name]=$_.'#text' }
[pscustomobject]@{ User=$d['TargetUserName']; IP=$d['IpAddress'] }
} | Group-Object User,IP | Sort-Object Count -Descending | Select-Object Count,Name
关键:登录类型(LogonType)的判读
| LogonType |
含义 |
取证价值 |
| 2 |
交互式(本地登录) |
有人坐在机器前 |
| 3 |
网络(访问共享/远程服务) |
可能是横向移动 |
| 4 |
批量(计划任务、脚本) |
自动化任务 |
| 5 |
服务 |
服务账户 |
| 7 |
解锁(解除工作站锁定) |
桌面会话活动 |
| 8 |
网络明文 |
凭据可能以明文传输,风险高 |
| 9 |
新凭据(New Credentials,runas) |
提权操作 |
| 10 |
远程桌面(RDPInteractive) |
远程会话 |
| 11 |
缓存凭据 |
离线解锁 |
3.5 第 5 步:Sysmon 日志(若系统部署了 Sysmon)
Sysmon 写在独立通道 Microsoft-Windows-Sysmon/Operational,默认不存在;涉及入侵的案件应优先检查,详见 04。