Windows预取文件默认存储在系统目录的Prefetch文件夹下,后缀为.pf,文件头包含魔数、版本、程序执行次数等基础信息,后续部分存储程序路径、关联的文件块等数据,解析这些二进制内容就能还原程序的执行相关记录。

PF文件的基础结构说明
PF文件采用二进制格式存储,不同版本的Windows对应的文件结构略有差异,主流的版本有17、23、26等,解析前需要先判断文件版本。文件的核心结构分为几个部分:
- 文件头:前84字节左右,包含魔数、版本号、程序执行次数、最近执行时间等字段
- 程序路径信息:存储程序的完整执行路径,采用UTF-16编码
- 关联文件块:记录程序启动时加载的相关文件信息,用于系统预加载加速
C#读取PF文件的核心步骤
1. 读取文件二进制内容
首先需要以二进制模式打开.pf文件,读取全部字节到内存中,方便后续解析。需要注意的是读取系统目录下的预取文件需要管理员权限,否则会抛出访问异常。
using System;
using System.IO;
public class PfFileReader
{
public byte[] ReadPfFile(string filePath)
{
// 检查文件是否存在
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"预取文件不存在:{filePath}");
}
// 读取全部二进制内容
return File.ReadAllBytes(filePath);
}
}
2. 解析文件头信息
文件头的前4字节是魔数,固定为0x41434353,对应的ASCII字符串是SCCA,用于校验文件是否为合法的PF文件。第5到8字节是版本号,后续可以解析执行次数、最近执行时间等字段。
using System;
using System.Text;
public class PfHeaderParser
{
public PfHeader ParseHeader(byte[] fileBytes)
{
PfHeader header = new PfHeader();
// 校验魔数
byte[] magicBytes = new byte[4];
Array.Copy(fileBytes, 0, magicBytes, 0, 4);
string magic = Encoding.ASCII.GetString(magicBytes);
if (magic != "SCCA")
{
throw new InvalidDataException("不是合法的Windows预取文件");
}
header.Magic = magic;
// 解析版本号,占用4字节,小端序
header.Version = BitConverter.ToInt32(fileBytes, 4);
// 解析程序执行次数,不同版本偏移不同,这里以版本17为例,偏移0x10
if (header.Version == 17)
{
header.RunCount = BitConverter.ToInt32(fileBytes, 0x10);
}
else if (header.Version == 23)
{
header.RunCount = BitConverter.ToInt32(fileBytes, 0x18);
}
return header;
}
}
public class PfHeader
{
public string Magic { get; set; }
public int Version { get; set; }
public int RunCount { get; set; }
}
3. 解析程序路径
程序路径的偏移位置也和版本相关,版本17中路径偏移为0x3C,路径长度偏移为0x38,路径采用UTF-16小端编码存储,解析时需要按照对应编码转换字节数组。
using System;
using System.Text;
public class PfPathParser
{
public string ParseProgramPath(byte[] fileBytes, int version)
{
int pathOffset = 0;
int pathLength = 0;
if (version == 17)
{
pathOffset = BitConverter.ToInt32(fileBytes, 0x3C);
pathLength = BitConverter.ToInt32(fileBytes, 0x38);
}
else if (version == 23)
{
pathOffset = BitConverter.ToInt32(fileBytes, 0x44);
pathLength = BitConverter.ToInt32(fileBytes, 0x40);
}
else
{
throw new NotSupportedException($"不支持的PF文件版本:{version}");
}
// 路径长度单位是字符数,UTF-16每个字符2字节,所以字节长度是pathLength * 2
byte[] pathBytes = new byte[pathLength * 2];
Array.Copy(fileBytes, pathOffset, pathBytes, 0, pathBytes.Length);
// 转换为UTF-16字符串,去掉末尾的空字符
string path = Encoding.Unicode.GetString(pathBytes).TrimEnd(' ');
return path;
}
}
4. 完整调用示例
将上面的解析逻辑组合起来,就可以读取指定PF文件的程序执行信息,输出程序路径和执行次数。
using System;
using System.IO;
class Program
{
static void Main()
{
try
{
// 这里替换为实际的预取文件路径,比如C:WindowsPrefetchNOTEPAD.EXE-2D12D8D1.pf
string pfFilePath = @"C:WindowsPrefetchNOTEPAD.EXE-2D12D8D1.pf";
PfFileReader reader = new PfFileReader();
byte[] fileBytes = reader.ReadPfFile(pfFilePath);
PfHeaderParser headerParser = new PfHeaderParser();
PfHeader header = headerParser.ParseHeader(fileBytes);
PfPathParser pathParser = new PfPathParser();
string programPath = pathParser.ParseProgramPath(fileBytes, header.Version);
Console.WriteLine($"PF文件版本:{header.Version}");
Console.WriteLine($"程序执行次数:{header.RunCount}");
Console.WriteLine($"程序路径:{programPath}");
}
catch (Exception ex)
{
Console.WriteLine($"解析失败:{ex.Message}");
}
}
}
注意事项
解析PF文件时需要注意几个问题,首先是要获取管理员权限才能访问系统Prefetch目录下的文件,其次是不同Windows版本的PF文件结构可能有差异,实际使用时需要先判断版本再选择对应的解析偏移,另外部分PF文件可能已经被系统清理或者损坏,需要做异常处理避免程序崩溃。
C#Windows预取文件pf文件解析程序执行分析修改时间:2026-06-12 20:39:35