一、概述
Windows 取证里最容易被误读的执行证据,就是 Prefetch。
系统为加快程序启动而记录每个可执行文件的上次运行时间与运行次数,这些记录以独立的 .pf 二进制文件存放在 C:\Windows\Prefetch\,不在事件日志里,不随日志轮转消失,也不容易被普通用户察觉。「某个可疑程序到底跑没跑过」——这个问题上它往往是第一顺位答案。
三个特性决定了它极易被误判:
- 它是缓存,不是审计日志。 系统追求「启动快」,不为「记录全」服务。
- 它有六个格式版本,且带压缩层。 从 XP 到 Windows 11 跨越 v17 到 v31,Windows 8.1 之后还额外套了一层 MAM 压缩。
- 它可能完全不存在。 被显式关闭时目录为空或不存在;被清理后也会留下痕迹。
方法论只有一句:「Prefetch 里有」≠「全部执行记录」,「Prefetch 里没有」≠「没执行过」。 两个方向都会出错,而第二个方向在实战中更常导致错误结论。
它处于分析环节的最前端之一——执行事实是后续所有排查的收敛条件:
- 上游依赖两件事。第一步必须确认 Prefetch 状态(
EnablePrefetcher 的四个取值决定了目录里有没有东西),这属于卷与工件准备,见Windows 取证工件地图;Windows 版本判定决定了用哪个解析器,用某一版本的固定偏移去读另一版本会得到看似合理实则完全错误的数据。
- 它自己要按顺序做三件事:先看偏移 0 的魔数再选解析器(
0x11 / 0x17 / 0x1A / 0x1E / 0x1F 或 MAM\x04);再确认是否需要解压;最后才读字段。
- 下游支撑 三处。执行时间进入时间线(FILETIME 精确到 100 纳秒,是少数高精度时间源);执行路径是持久化排查的入口(「这个程序从哪路径启动」直接指向可执行文件来源);ShimCache 与 Amcache是交叉验证的同伴,三者的可信度各不相同。
落到具体格式上:
| 魔数(偏移 0) |
含义 |
对应系统 |
0x11 |
v17,未压缩 |
Windows XP / 2003 |
0x17 |
v23,未压缩 |
Windows Vista / 7 |
0x1A |
v26,未压缩 |
Windows 8 / 8.1 |
0x1E |
v30,未压缩 |
Windows 10,以及早期 Windows 11 |
0x1F |
v31,未压缩 |
较新的 Windows 11 构建 |
4D 41 4D 04(MAM\x04) |
MAM 压缩封装,解压后才是上述结构 |
Windows 8.1 起 |
| 落点 |
C:\Windows\Prefetch\*.pf |
文件名是程序名的清洗形式,含 ADS 执行时的特殊形式 |
| 签名 |
未压缩文件偏移 4 处为 ASCII SCCA |
判别格式是否正常 |
| 关键字段 |
执行时间槽(最多 8 个)、Run Count、引用路径数组 |
各版本偏移完全不同 |
| 开关 |
注册表 EnablePrefetcher(四个取值) |
决定目录里有没有内容 |
三条规律值得单独拎出来。先看魔数再选解析器,选错解析器后面读出来的全是错的。偏移 0 不是已知魔数、但偏移 4 冒出 SCCA 的,格式仍然正常,别急着判损坏。MAM 封装用文本编辑器打开是一片乱码,那是封装本来的样子,不是文件被破坏了。漏掉任何一条,后面读出来的字段都会是错的,而且错得很像真的。
证明力按维度分是这样的:
| 维度 |
证明力 |
说明 |
| 程序被启动过 |
强 |
核心价值 |
| 执行的完整路径 |
强 |
在文件内容的路径字符串中 |
| 执行次数 |
中 |
Run Count 精确;时间戳只有最近 8 次 |
| 执行的具体时间 |
强 |
FILETIME,精确到 100 纳秒 |
| 执行是否成功 |
不能证明 |
启动即崩溃、缺 DLL、被杀都会留记录 |
| 谁执行的 |
弱 |
不带用户名,需与事件日志、用户目录交叉 |
| 程序是否恶意 |
零 |
中性的执行事实,定性需哈希查库与行为证据 |
外加三个必须写进报告的语义边界:
- Windows 8 之后每个程序最多保存 8 个最近运行时间戳,第 9 次执行覆盖最旧槽位。当
Run Count 大于 8 时,绝不能写「只运行了 8 次」;反之当 Run Count 小于 8 时全部执行时间可见,此时次数是精确的。
- Prefetch 证明「发生了启动」,不证明「完成了操作」。 报告里写「该恶意程序被执行」是危险推断,正确写法是「该路径下的可执行文件被启动,时间为 X,累计 N 次」。
- ADS 执行会产生特殊文件名,若不知道这个规则,解析器可能报出无法归一的文件名。
读到这里你应该已经理解:NTFS 的元数据与 MFT 概念,Prefetch 文件的读取依赖正确的卷接入,见NTFS 结构与 MFT 解析;FILETIME 的纪元与 100 纳秒精度,这是时间字段的基础,见时间戳换算;注册表的读取方式,EnablePrefetcher 是判断 Prefetch 是否存在的前提,见注册表取证;十六进制查看器的用法,判断魔数与签名这一步不能跳过;以及 MAM 压缩的封装结构,否则会把正常格式误判为文件损坏。
二、核心原理
2.1 Prefetch 解决什么问题
Windows 启动时要把待加载的 DLL 从磁盘读入并重新安排内存布局,这很慢。
Prefetch 的思路是把上次启动时加载了什么记下来,下次照做,省掉重复计算。
为此系统为每个可执行文件建立一个独立的 .pf 文件,而不是一个集中的数据库——这是为了快速随机访问。
| 特性 |
说明 |
| 位置 |
C:\Windows\Prefetch\*.pf(系统卷所在卷) |
| 文件名 |
<程序名>-<路径哈希>.pf,如 NOTEPAD.EXE-1A2B3C4D.pf |
| 写入时机 |
程序启动后由内存管理器的 Prefetcher 组件写入 |
| 记录内容 |
运行时间戳、运行次数、引用的文件列表、卷信息、目录字符串 |
| 目录容量 |
XP 至 Windows 8 为 128 项;Windows 10 及以后为 1024 项 |
| 不是 |
审计日志、用户行为记录、成功/失败判定 |
路径哈希的作用是去重:同名程序从不同路径执行会生成不同哈希、不同 .pf 文件。
因此不能只按程序名去查,必须确认路径哈希是否匹配。
2.2 格式版本与魔数判别
这是动手前必须掌握的一张表。先看魔数,再选解析器:
| 魔数(偏移 0) |
含义 |
对应系统 |
0x11 |
v17,未压缩 |
Windows XP / 2003 |
0x17 |
v23,未压缩 |
Windows Vista / 7 |
0x1A |
v26,未压缩 |
Windows 8 / 8.1 |
0x1E |
v30,未压缩 |
Windows 10,以及早期 Windows 11 |
0x1F |
v31,未压缩 |
较新的 Windows 11 构建 |
4D 41 4D 04(MAM\x04) |
MAM 压缩封装,解压后才是上述结构 |
Windows 8.1 起 |
两条规律:
- 未压缩文件的偏移 4 处是 ASCII
SCCA 签名。 若偏移 0 不是已知魔数而偏移 4 出现 SCCA,格式正常。
- Windows 8.1 及以后普遍使用 MAM 封装(Microsoft XPRESS Huffman / LZXPRESS 压缩)。此时用文本编辑器或十六进制查看器打开显示为乱码是正常格式,不是文件损坏。必须先解压再解析。
不同版本中,文件信息区各字段(执行时间、计数器、路径数组)的偏移完全不同。
用某一版本的固定偏移去读另一版本,会得到看似合理实则完全错误的数据。
2.3 陷阱一:8 槽环形覆盖
这是 Prefetch 最重要的语义。
Windows 8 之后,Prefetch 为每个程序保存最多 8 个最近运行时间戳。第 9 次执行时,最旧的槽位被新的覆盖。因此:
| 字段 |
语义 |
正确表述 |
Run Count |
累计执行次数计数器,不受槽位限制 |
「累计 N 次」 |
| 8 个时间槽 |
仅最近 8 次的时间 |
「最近 8 次为……」 |
由此推出报告中的关键纪律:
- 次数必须用
Run Count,不能用时间戳个数;
- 时间只能说最近 8 次,更早的时间不可知;
- 当
Run Count 大于 8 时,绝不能写「只运行了 8 次」;
- 反之,当
Run Count 小于 8 时,全部执行时间都可见,此时次数是精确的。
2.4 陷阱二:EnablePrefetcher 的四个取值
Prefetch 可被显式关闭,配置位于:
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters
EnablePrefetcher (REG_DWORD)
| 值 |
含义 |
0 |
完全禁用 |
1 |
仅启用应用程序启动预取 |
2 |
仅启用启动(Boot)预取 |
3 |
应用程序启动预取 + 启动预取(桌面版默认值) |
这是一个极易出错的地方。
看到 EnablePrefetcher = 2 就写「Prefetch 已启用」是错的——2 表示只有启动预取,不包含应用程序启动预取,恰恰意味着程序执行痕迹不会被记录。
只有 1 或 3 才包含应用预取,桌面版默认是 3。
> 3 的值不会带来额外收益。
活体系统上可直接查询,离线检材需从 SYSTEM hive 的当前 ControlSet 中读取:
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters" /v EnablePrefetcher
离线时常见的错误是读了 ControlSet001 却不知道当前是哪个 ControlSet——应先查 Select\Current 确认。
2.5 陷阱三:ADS 执行产生的特殊文件名
当程序通过 NTFS 备用数据流(ADS)执行时,Prefetch 文件名不再是常规的 程序名-哈希.pf,而会出现冒号分隔的形态:
notepad.exe:evil.pf
└── 主数据流 ──┘ └─ ADS 名称 ─┘
(主数据流名保留,冒号后是数据流名)
fls 对这类条目的呈现形式与普通文件的 名称-地址-序列 模式不同,且不同工具的显示方式不一致。
因此:
- 不要假设冒号一定在「程序名与哈希之间」。有的工具把主数据流名与数据流名一起显示,有的会截断或转义,必须用
fls 原始输出与 istat 交叉确认。
- 不要只用
*.pf 结尾过滤。有的实现把 .pf 放在数据流名上(如上例),有的放在主名上,只按后缀过滤必然漏项。
- 正确做法:枚举 Prefetch 目录时保留含冒号的完整条目,再用
istat 确认其是否为 ADS 形式,并与 NTFS 的 ADS 列表交叉验证。
发现形如 程序名.exe:流名 的条目,是存在 ADS 执行行为的强指示——这类执行常用于载荷隐藏,正常业务软件极少这样启动。
2.6 Prefetch 的证明力边界
| 维度 |
证明力 |
说明 |
| 程序被启动过 |
强 |
核心价值 |
| 执行的完整路径 |
强 |
在文件内容的路径字符串中 |
| 执行次数 |
中 |
Run Count 精确;时间戳只有最近 8 次 |
| 执行的具体时间 |
强 |
FILETIME,精确到 100 纳秒 |
| 执行是否成功 |
不能证明 |
启动即崩溃、缺 DLL、被杀都会留记录 |
| 谁执行的 |
弱 |
不带用户名,需与事件日志、LNK 用户目录交叉 |
| 程序是否恶意 |
零 |
中性的执行事实,定性需哈希查库与行为证据 |
本篇最需要传达的思维:Prefetch 证明「发生了启动」,不证明「完成了操作」。
报告里写「该恶意程序被执行」是危险推断;正确写法是「该路径下的可执行文件被启动,时间为 X,累计 N 次」。
三、操作步骤
3.1 确认 Prefetch 状态(必做第一步)
这一步的结论决定后续所有分析是否有意义。离线检材从 SYSTEM hive 读取:
# 提取 SYSTEM hive
mmls /work/DigiForensics.dd
fsstat -o 2048 /work/DigiForensics.dd
fls -o 2048 -r -p /work/DigiForensics.dd \
| grep -E 'config/SYSTEM$' | grep -E '^r/r' | awk '{print $2}' \
| while read -r ino; do icat -o 2048 /work/DigiForensics.dd "$ino" > /evidence/hives/SYSTEM; done
# 先确认当前 ControlSet,再读 PrefetchParameters
rip.exe -p /evidence/hives/SYSTEM -a select.txt select
# \Select\Current : 1 ← 当前是 ControlSet001
rip.exe -p /evidence/hives/SYSTEM -a pf.txt prefetchparameters
# ControlSet001\Control\Session Manager\Memory Management\PrefetchParameters
# EnablePrefetcher = 3 ← 应用+启动预取全启用
判定规则:0 → Prefetch 路线不可用,不得做否定性结论;1 或 3 → 可正常依赖;2 → 只有启动预取,应用执行痕迹不会记录。
3.2 枚举并提取
# 枚举 Prefetch 目录,保留完整文件名(含冒号)
fls -o 2048 -r -p /work/DigiForensics.dd \
| grep -E '/Windows/Prefetch/' \
| grep -E '^r/r' | awk '{print $2}' \
| while read -r ino; do
icat -o 2048 /work/DigiForensics.dd "$ino" > "/evidence/prefetch/$(printf '%s' "${ino%%:*}").pf"
done
# 保留原始文件名列表(含 ADS 冒号形式)
fls -o 2048 -r -p /work/DigiForensics.dd | grep -E '/Windows/Prefetch/' > /evidence/prefetch/names.txt
不要只用 *.pf 过滤——那会漏掉 ADS 形态的文件。同时记录每个文件的 MFT 时间戳,用于判断「很久没运行」还是「近期被批量清理过」。
3.3 判定格式版本
抽一个文件看魔数,再决定用哪种解析路径:
xxd -l 4 /evidence/prefetch/sample.pf
# 4D 41 4D 04 ← "MAM\x04",MAM 压缩封装,必须先解压
# 或 1E 00 00 00 ← v30,未压缩,可直接按 v30 布局解析
统计全目录的版本分布,判断检材是否混版:
for f in /evidence/prefetch/*.pf; do
printf "%-40s %s\
" "$(basename "$f")" "$(xxd -l 4 -p "$f")"
done | sort -k2 | uniq -c -f1 | sort -rn
3.4 解析
PECmd 会自动处理 MAM 解压,按版本选择解析布局:
# 解析整个目录,输出 CSV
PECmd.exe -d "C:\evidence\prefetch" --csv "C:\evidence\out" --csvf "pf.csv"
# 解析单个文件并输出时间线
PECmd.exe -f "C:\evidence\prefetch\SUSPICIOUS.EXE-4A5B6C7D.pf" --mp --dt "yyyy-MM-dd HH:mm:ss"
输出列应包含:程序名、Run Count、Last Run Time、Previous Run Times(v26+ 有多个时间槽)、完整路径、引用文件数、卷信息。
3.5 交叉验证
Prefetch 不带用户名,且不说明执行结果,必须交叉:
- LNK / Jump List:确认是否 GUI 方式打开,以及打开的具体文件。
- 事件日志 4624:确认案发时段有对应账户的登录会话。
- Amcache:提供 SHA-1 哈希与 PE 编译时间,可与 Prefetch 的首次执行时间互证。
$UsnJrnl:确认可执行文件本身何时被创建或写入。
- SYSTEM hive 的
ShutdownTime:确认最后一次正常关机,判断断电影响的范围。
四、常见陷阱
4.1 采集前置与格式判别
陷阱 1:跳过 EnablePrefetcher 判定,把"没找到 .pf"写成"没执行过"
现象
在镜像里 C:\Windows\Prefetch\ 下没找到