Linux 时间线重建

Windows 侧做时间线有个便利:事件日志带年份、带时区、精度到毫秒,还集中在少数几个文件里。导出即完整时间线。

最后更新 2026-10-09版本 v1.0维护 DigiForensics查看历史 0

关键词:时间线、atime、mtime、ctime、crtime、relatime、noatime、时区统一、super-timeline、收敛分析 难度:高级 前置知识:ext4 inode 结构、Linux 日志分析、时区与 epoch 换算

一、概述

Windows 侧做时间线有个便利:事件日志带年份、带时区、精度到毫秒,还集中在少数几个文件里。导出即完整时间线。

Linux 没有这个便利。时间证据散落在八个互不相干的源里,各有各的精度,也各有各的缺陷:

数据源 年份 精度 已知缺陷
文件 mtime / ctime / crtime ✅ 纳秒(ext4) crtime 仅 ext3+ 有
文件 atime ✅ 纳秒 取决于挂载选项,可能不可信
auth.log / syslog ❌ 无年份 秒 格式五花八门
journalctl ✅ 微秒 + 时区 可能只存在于内存,断电即失
wtmp / bash_history ✅ 秒 二进制;仅正常退出时落盘
审计日志 audit.log ✅ 秒 未启用则无;且是二进制

没有任何一个文件能给出完整时间线。必须把多个源对齐、互相补足,而对齐的前提是统一时间基准。

这一层处在分析环节的综合阶段,在 Linux 线路上是收口位置:前面的结构解析、日志分析、工件清点都只产出时间戳,合并成一条可读的事件序列是这一步独有的工作。它同时是两个方向的交汇点:

  • 横向汇聚 —— 文件系统时间戳、系统日志、登录记录、审计记录、包管理器账本,五类源各自独立,没有第二处做同样的事
  • 纵向支撑 —— 下游的行为认定(登录之后做了什么、持久化何时建立)依赖这条时间线;时间线错了,行为认定全错

上游是 系统日志深度分析 的日志导出与 ext4 结构与 inode 的时间戳提取,字段语义在两篇里已有,这里不重复 inode 布局与日志路径。

具体落到可用的字段上,有这么几类。

  • 四个文件时间戳 —— atime(访问)、mtime(内容修改)、ctime(inode 变更)、crtime(创建,仅 ext3+)。四个字段语义各不相同,误用会造出不存在的事件
  • ctime 的特殊地位 —— 它在改权限、改属主、改链接数时都会更新。"ctime 晚于 mtime 21 秒"是"SUID 位被单独赋予"的判据,而不是"内容被改过"
  • crtime 与 mtime 的差值 —— crtime 老而 mtime 新 = 篡改已有文件;两者接近 = 新建文件。第五章案例里 authorized_keys 正是靠这一点被判定为"追加公钥"
  • atime 与挂载选项 —— relatime(只在一日之内或超过 24 小时才更新)、noatime(完全不更新)、atime(每次都更新)。默认值不是 atime,所以 atime 在现代 Linux 上基本不可信
  • /etc/fstab —— 挂载选项的书面证据。atime 类结论的强度完全取决于能不能引到这一行
  • 时区标识 —— /etc/timezone、/etc/localtime 符号链接目标、日志里的 UTC / CST 标记。"CST" 在英文语境下可指中国标准时间也可指北美中部时间,混淆会让整条时间线偏移 13 或 14 小时
  • 纳秒与微秒字段 —— ext4 crtime 尾部的纳秒值、journal 的微秒精度。精度差异是判断两个事件是否同时发生的基础
  • bash_history 的 #<epoch> 注释行 —— bash 自己记录的秒级 epoch,不依赖日志格式,是 auth.log 之外的独立时间源
  • stat 与 find -printf 的批量输出 —— 提取手段。注意挂载点上的 stat 会触发 atime 更新(取决于挂载选项),所以必须在只读挂载上做
  • 各源的收敛产物 —— 统一时区、统一格式、带"支撑源数量"标记的合并时间线

这套东西答得出来的是:某段时间内这台机器上发生过什么、按什么顺序、每个结论有几个独立源支撑、以及哪些时段完全没有记录。

答不出来的,同样得说清:

  • "某人在某天读了某文件" —— relatime / noatime 下 atime 不随读更新,这个推论在默认配置下不成立。要读文件访问记录要走审计日志
  • 命令的完整执行内容 —— bash_history 只在正常退出时落盘,强制断电或 HISTFILE=/dev/null 会让历史归零。第五章案例里攻击者就做了后者
  • 跨时区/跨系统的绝对一致 —— NTP 跳变、手动改系统时间都会造成偏移,NTP 调整记录本身是另一个证据源
  • "没有事件" —— 必须写成"各源均无记录"。日志缺失、审计未启用、会话被强杀都可能导致空窗,空窗是证据缺口不是阴性结论
  • sub-second 级的事件先后 —— 只有 journal 与 ext4 crtime 有亚秒精度,多数源只能给到秒。秒内顺序无法判定时必须说明
  • 攻击者身份 —— 时间线给的是顺序与时间,归属要靠账户、认证方式、命令内容

前提有三样,缺一不可:已读 ext4 结构与 inode,四个时间戳的字段位置与语义在那里;已读 系统日志深度分析,知道各日志源在哪、精度如何、哪些可能缺失;会处理时区与 epoch 换算。

最后一条最容易被跳过,也最容易翻车。时区搞错时,整条时间线依然"看起来合理",只是全部偏移了几小时。 不熟就先看 时间戳换算 打底。所有合并操作都建立在"时间已经归一到同一基准"这个前提上,前提错了后面全白做。

二、核心原理

2.1 Linux 时间线的数据源矩阵

数据源 精度 有年份 有时区 可信度 清理难度
auth.log / secure 秒 ❌ 无 ❌ 本地时间 高(除非被清) ★★★
journalctl(持久化 journal) 微秒 ✅ ✅ 高 ★★★
journal(仅内存 /run) 微秒 ✅ ✅ 断电即全失 —
utmp(当前会话) 秒 ✅ — 仅运行中有效 重启清空
wtmp(成功登录) 秒 ✅ ❌ 高 ★★
btmp(失败登录) 秒 ✅ ❌ 高 ★★
bash_history 的 #epoch 秒 ✅(epoch) ✅(UTC) 仅当设了 HISTTIMEFORMAT ★
.zsh_history 秒 ✅ ✅ 高(默认带时间戳) ★
apt/history.log 秒 ✅ ✅(标注时区) 高 ★★
文件 mtime / crtime 纳秒(ext4) ✅ ❌ 高 ★★★★
文件 atime 纳秒 ✅ ❌ ⚠️ 低(见 2.4) ★★★★
文件 ctime 纳秒 ✅ ❌ 中(易误解) ★★★★
lastlog 秒 ✅ ❌ 高 ★★
/proc 在线快照 秒(采集时刻) ✅ ✅ 高(但仅在采集时刻) 进程退出即失

读表要点:没有任何单一源是完整的。auth.log 最全但无年份;journal 有年份但可能只在内存;文件时间戳精度最高但只覆盖文件操作。必须组合,且每个源都要标注其缺陷。

2.2 四个时间戳的精确语义

本篇最基础、也最容易错的部分:

时间戳 精确含义 什么时候变 什么时候不变
atime 最后一次读取/访问 文件数据被 read() 打开并读取时(受挂载选项影响) 纯 stat 不一定改(取决于挂载选项)
mtime 最后一次内容修改(数据写入) 文件数据被写时 改权限、改属主时不变
ctime 最后一次 inode 元数据变更 chmod/chown/chmod/ln(改链接数)/mtime 变化时 纯读操作不变
crtime 文件创建时间(ext4 特有) 文件被创建时(creat/open+O_CREAT/在 O_EXCL 下新建) 此后永不改变

必须牢记的三条:

① ctime 不是创建时间。

它是"元数据变更时间"。chmod 4755 /tmp/x 会更新 ctime 但不更新 mtime。这个差异是识别"改权限不改内容"的判据(见第 01 篇的案例)。

真正的创建时间是 crtime(ext4)。

② mtime 不变不代表内容没被改。

可以只改 atime(touch -a)而不动 mtime。所以 mtime 早于某个"应该写文件"的事件时,要考虑 touch 伪造。

检查 ctime 是否比 mtime 新很多——若是,说明有 touch 操作。

③ 复制文件会重置时间戳。

cp(不带 -p)会把新文件的 atime/mtime 设为当前时间,crtime 也为当前时间。所以**"某文件的 crtime = 拷贝时刻"**。

cp -p 保留 mtime 但 crtime 仍为拷贝时刻。** crtime 因此可以揭示"这个文件是从别处拷来的"。**

2.3 ext4 的 crtime 与亚秒精度

ext4 在 extra inode 里加了 i_crtime(0x90)和 i_crtime_extra(0x94),同时 i_atime/i_mtime/i_ctime 各有 _extra 字段存纳秒的高位。

两点取证价值:

  1. crtime 是 ext4 独有的"真创建时间"。ext2/ext3 没有这个字段(此时 stat 只显示三个时间)。
  2. 亚秒精度:istat 输出中若出现 ... nano second resolution 行,说明是 ext4。秒级无法区分"同一秒内批量创建的文件",纳秒级可以给出先后顺序——对脚本批量落地文件的场景(恶意软件复制自身、解压释放载荷),纳秒顺序是"先创建谁"的强证据。

⚠️ crtime 在文件被移动/重命名时不变(mv 改的是目录项)。所以"crtime 很老但路径很深"可能只是被移动过,不能仅凭 crtime 早就说"文件很老"。

2.4 ★ relatime / noatime:atime 可信度的决定因素

这是 Linux 时间线最关键的实践要点。

早期 Linux 每次读文件都更新 atime(atime 行为),后来因为性能问题引入了优化挂载选项:

挂载选项 atime 更新规则 对取证的影响
默认(无选项) 每次读都更新 atime 可用(但性能差,现代系统少用)
relatime 仅当 atime 比 mtime/ctime 都早 24 小时以上才更新 atime 大致可用但"最后读取时间"会被钝化(一天粒度)
noatime 完全不更新 atime 完全不可用(Ubuntu 服务器常见)
strictatime 每次读都更新(等价于默认,但显式) atime 可用

如何确认本机用的是哪种:

离线:从 /etc/fstab 判断

grep -E '^\S+\s+/\s+' /mnt/df/etc/fstab

/dev/sda1 / ext4 defaults 0 1 ← 用默认(现代 Debian/Ubuntu 的默认其实是 relatime 隐含)

/dev/sda1 / ext4 noatime 0 1 ← noatime,atime 完全不可用

在线:/proc/mounts 会显示实际生效的选项

grep ' / ' /proc/mounts

/dev/sda1 / ext4 rw,relatime,errors=remount-ro 0 0 ← relatime

另可查 systemd 挂载选项 / fstab 生成来源

取证结论的措辞必须相应调整:

情况 能得出的结论 不能得出的结论
noatime "atime 是创建或最后修改时的遗留值,不代表任何读取行为" ❌ "文件从 X 到 Y 期间没被读过"
relatime "atime 大致是最后读取时间的上界(可能钝化到天)" ❌ 精确到秒的"最后读取时刻"
默认/strictatime "atime = 最后读取时间" 仍需注意读取本身也可能被证据保全动作改变

本知识库的核心立场:除非明确确认是 strictatime/默认挂载,否则 atime 只能作为弱证据("不早于某时刻"),不能作为"某人在某时读过"的证明。 报告里应写明本机挂载选项,让读者自行判断 atime 证据强度。

2.5 时区统一:跨源对齐的前提

不同源的时间基准不同,对齐前必须统一。统一流程分三步。

第 1 步:确定本机时区

cat /mnt/df/etc/timezone # → Asia/Shanghai(文件内容) ls -l /mnt/df/etc/localtime # → 软链到 /usr/share/zoneinfo/Asia/Shanghai

zoneinfo 路径才是权威(timezone 文件可能缺失或过时)

若 localtime 是普通文件(不是软链),用 zdump 或对照 /usr/share/zoneinfo 的内容判断

第 2 步:把各源归类

源类型 基准 处理方式
文件时间戳(stat) 本机本地时间,无时区标记 按本机时区转 UTC
auth.log(无年份) 本地时间 补年份 + 转 UTC(年份靠 journal 或归档推断)
journalctl -o short-precise 带时区偏移 直接按偏移转 UTC(最省事)
apt/history.log 本地时间 + 时区缩写 按缩写 + /etc/timezone 确认后转 UTC
bash_history #epoch UTC(epoch 本身无时区) 直接是 UTC,无需转换 ★
wtmp/btmp(last 输出) 本地时间 按本机时区转 UTC

第 3 步:转换(统一到 UTC 或统一到本地,选一种并全表一致)

★ 正确做法:先让 date 按「源时区」解释输入,再按 UTC 输出

TZ=Asia/Shanghai date -d '2024-03-17 22:47:33' '+%Y-%m-%dT%H:%M:%SZ'

2024-03-17T14:47:33Z ← 本地 22:47:33 (UTC+8) → UTC 14:47:33 ✓

源本来就是 UTC 时(如 journal 的 ISO 行、bash_history 的 #epoch)

TZ=UTC date -d '2024-03-17T14:47:33+08:00' '+%Y-%m-%dT%H:%M:%SZ'

2024-03-17T06:47:33Z

TZ=UTC date -d @1710686853 '+%Y-%m-%dT%H:%M:%SZ' # epoch 本身即 UTC

2024-03-17T14:47:33Z

反向(UTC → 本地显示)

TZ=Asia/Shanghai date -d @1710686853 '+%F %T %Z%z'

2024-03-17 22:47:33 CST+0800

⚠️ TZ=UTC date -d '2024-03-17 22:47:33' 不会做转换。 TZ 只改变"-d 字符串被当作哪个时区的墙钟"以及"输出如何渲染",它不会把 Asia/Shanghai 的本地时间自动换算成 UTC——那样会原样输出 22:47:33Z,整整差 8 小时。正确写法必须显式写出源时区(TZ=Asia/Shanghai date -d '…'),这是 Linux 取证时间线最隐蔽的错误来源之一。

⚠️ epoch 换算不需要任何时区转换。 #1710686853 定义为自 1970-01-01T00:00:00Z 起的秒数,本身即 UTC。但 date -d @<epoch> 默认按本地时区渲染,要输出 UTC 必须加 TZ=UTC 或 -u。

2.6 与 Windows 时间线的根本差异

对比 Windows Linux
集中事件日志 .evtx,单一入口 ❌ 无;分散在文本日志 + 二进制 utmp/wtmp
事件时间精度 毫秒 多数秒;journal 微秒;ext4 文件纳秒
时区 事件自带偏移,可精确还原 多数无时区,需靠本机时区推断
年份 永远有 ** auth.log 无**(靠 journal 补)
集中化安全审计 组策略 + 事件转发 auditd(可选)+ journald,可集中转发
时间篡改检测 可用 $LogFile/UsnJrnl 检测回滚 弱;主要靠多源交叉

结论:Linux 时间线是"多源拼接 + 交叉验证"的产物,Windows 更接近"导出即用"。

Linux 取证在时间线上的工作量更大,对分析者的纪律要求更高——每个时间戳都要问"它的时区是什么、年份哪来的、精度多少、能不能被篡改"。


三、操作步骤

3.1 第 1 步:确定时区、挂载选项、文件系统状态(三个基础前提)

(1) 时区

cat /mnt/df/etc/timezone 2>/dev/null ls -l /mnt/df/etc/localtime 2>/dev/null # zoneinfo 软链是权威

(2) 挂载选项(决定 atime 可信度)

grep -E '^\S+\s+/\s+' /mnt/df/etc/fstab

/dev/sda1 / ext4 defaults 0 1 → 隐含 relatime

/dev/sda1 / ext