导读:本期聚焦于木下创作的《C# 如何解析 Windows Jumplist 文件并分析用户常用程序访问记录?》,敬请观看详情。直接读取扩展名为 .automaticDestinations-ms 的文件时会发现内部并不是文本,而是 OLE 复合文档结构,很多分析代码因此卡在第一步。本文从 Windows 跳转列表的存储路径讲起,说明自动目标列表和自定义目标列表的区别,并给出用 C# 解析复合文档流、提取 DestList 时间戳和 LNK 目标路径的完整思路。通过遍历 AutomaticDestinations 目录并还原 Shell Link 数据,可以分析出用户最近打开的文件、常用程序以及访问频率。文中包含可运行的 C# 代码,覆盖文件头识别、流枚举、二进制解析和异常处理等关键环节,同时提醒在取证或用户行为分析场景中必须注意授权合规。

Windows 跳转列表(Jumplist)是任务栏右键菜单里展示的最近访问文件与常用任务集合。系统将这些数据持久化到用户目录下,文件扩展名为 .automaticDestinations-ms 和 .customDestinations-ms。用 C# 分析这些文件能够还原用户最近打开的程序、文档和访问时间,适合用于电子取证、员工行为审计或本机使用习惯分析。不过这类文件不是普通文本,必须理解其复合文档结构并解析内部流,才能拿到有意义的信息。

C# 如何解析 Windows Jumplist 文件并分析用户常用程序访问记录?

Jumplist 文件存储位置与复合文档结构

自动目标列表文件存放于 C:\Users\你的用户名\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations 目录。自定义目标列表位于 C:\Users\你的用户名\AppData\Roaming\Microsoft\Windows\Recent\CustomDestinations。前者由系统根据应用用户模型 ID 自动生成,后者保存用户手动固定到跳转列表中的内容。

自动目标列表的文件名通常由应用用户模型 ID 经过哈希计算得到,形如 1b4dd67f29a37c5c.automaticDestinations-ms。每个文件对应一个应用或系统组件。这些文件并不是 XML 或 JSON,而是微软 OLE 复合文档格式(Compound File Binary,CFB)。文件头固定签名为十六进制 D0 CF 11 E0 A1 B1 1A E1,读取前 8 个字节即可确认文件类型。

复合文档内部由扇区、目录流、FAT 和 Mini FAT 组成,逻辑上可以看作一个微型文件系统。Jumplist 文件中的核心流包括 DestList 和多个以 16 字节标识符命名的流。DestList 保存最近使用列表的顺序、时间戳和目标 ID,其他流则保存对应目标的 Shell Link 数据。只有同时解析这两类流,才能把访问记录与具体文件路径对应起来。

用 C# 枚举并读取关键流

要读取复合文档,可以手动实现 CFB 解析器,但工作量较大。更实际的做法是引入 NuGet 包 OpenMCDF,它提供了轻量级的复合文档访问接口。使用 CompoundFile 类打开 .automaticDestinations-ms 文件,可以枚举所有流名称并读取字节。

先用文件头判断是否为有效复合文档,避免对无关文件进行解析。下面的代码展示如何验证签名并使用 OpenMCDF 枚举流:

using OpenMcdf;

byte[] signature = new byte[8] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
byte[] buffer = File.ReadAllBytes(filePath);
if (buffer.Length < 8 || !buffer.Take(8).SequenceEqual(signature))
{
    Console.WriteLine("不是有效的复合文档");
    return;
}
CompoundFile cf = new CompoundFile(filePath);
var streams = cf.RootStorage.GetStreams();
foreach (var stream in streams)
{
    Console.WriteLine(stream.Name);
}

枚举结果中通常能看到 DestList 以及多个 32 位十六进制字符串命名的流。DestList 流的前部没有分隔符,按固定长度顺序排列。每个条目通常包含 16 字节目标 ID、8 字节 FILETIME 时间戳以及若干标志位。解析时将流从头部开始循环读取,直到结束。

下面的代码读取 DestList 流并输出目标 ID 与最后访问时间:

CFStream destStream = cf.RootStorage.GetStream("DestList");
byte[] destData = destStream.GetData();
int offset = 0;
while (offset + 24 <= destData.Length)
{
    string targetId = BitConverter.ToString(destData, offset, 16).Replace("-", "");
    long fileTime = BitConverter.ToInt64(destData, offset + 16);
    DateTime lastAccess = DateTime.FromFileTimeUtc(fileTime).ToLocalTime();
    Console.WriteLine(targetId + " => " + lastAccess);
    offset += 24;
}

需要注意,DestList 条目大小在不同 Windows 版本中可能略有差异,实际开发时应根据文件大小和剩余字节做容错处理。读取时间戳后先转换为 UTC 再转本地时间,可以避免时区偏移导致的分析误差。

解析 LNK 流还原目标路径

DestList 中的目标 ID 对应复合文档中同名的流,这些流保存的是 Shell Link(LNK)二进制数据。LNK 文件有标准结构,头部为 76 字节的 ShellLinkHeader,其中偏移 20 处的 LinkFlags 字段决定后续包含哪些可选结构。

要提取目标路径,可以先判断 LinkFlags 中的 HasLinkTargetIDList 和 HasName 位是否置位。目标路径可能以 ANSI 或 Unicode 字符串形式出现在不同位置。对于大多数自动目标列表文件,路径信息位于 LinkInfo 结构或字符串数据区域中。下面的简化函数演示了如何从 LNK 数据中提取目标字符串:

private static string ReadLinkTarget(byte[] data)
{
    if (data.Length < 76) return string.Empty;
    uint linkFlags = BitConverter.ToUInt32(data, 20);
    bool isUnicode = (linkFlags & 0x00000080) != 0;
    int offset = 76;
    if ((linkFlags & 0x00000001) != 0)
    {
        ushort idListSize = BitConverter.ToUInt16(data, offset);
        offset += idListSize + 2;
    }
    if ((linkFlags & 0x00000002) != 0)
    {
        int localBasePathOffset = BitConverter.ToInt32(data, offset + 20);
        int basePathOffset = offset + localBasePathOffset;
        if (basePathOffset < data.Length)
        {
            if (isUnicode)
                return System.Text.Encoding.Unicode.GetString(data, basePathOffset, data.Length - basePathOffset).TrimEnd('\0');
            return System.Text.Encoding.Default.GetString(data, basePathOffset, data.Length - basePathOffset).TrimEnd('\0');
        }
    }
    return string.Empty;
}

提取出的路径可能是绝对路径,也可能是含有环境变量的相对路径。对于某些 UWP 应用,路径可能指向 shell:AppsFolder 下的应用程序标识,需要进一步调用系统 API 才能获得可读名称。解析 LNK 时还要注意 Volume ID 结构,它包含驱动器序列号和卷标,有时路径并不是直接可用的本地路径,需要结合卷信息还原。

将每个 DestList 条目的 ID 传入上述函数并对应输出,就可以得到类似 C:\Users\你的用户名\Documents\报告.docx 或 C:\Program Files\Notepad++\notepad++.exe 的结果。这样就把 16 字节的抽象 ID 与用户实际打开的对象关联起来。

统计访问频率与合规注意事项

拿到所有条目的时间戳和目标路径后,可以按路径分组统计访问次数,并按最后访问时间排序,输出用户常用文件和程序列表。例如遍历 AutomaticDestinations 目录下的每个复合文档,聚合 DestList 中的记录,再用目标 ID 关联 LNK 流,形成完整的数据集。

string automaticPath = @"C:\Users\你的用户名\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations";
var accessCount = new Dictionary<string, int>();
foreach (string file in Directory.GetFiles(automaticPath, "*.automaticDestinations-ms"))
{
    try
    {
        CompoundFile cf = new CompoundFile(file);
        // 此处省略解析 DestList 和 LNK 流的具体过程
        foreach (string path in parsedPaths)
        {
            if (accessCount.ContainsKey(path))
                accessCount[path]++;
            else
                accessCount[path] = 1;
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("跳过文件 " + file + ",原因:" + ex.Message);
    }
}

这段代码使用 Dictionary<string, int> 做频率统计,显示时可按值倒序排列。需要注意,复合文档可能被正在运行的应用锁定,打开时应使用 FileShare.ReadWrite 参数。代码示例中为集中展示核心逻辑省略了流解析部分,实际项目应将解析函数封装成独立类,方便复用。

Windows Jumplist 包含大量用户行为信息,属于个人隐私数据。在电子取证、内部审计或家长监控等场景中使用前,必须获得明确授权或具备合法依据。切勿将分析工具用于未授权的监控或数据窃取。技术上还应考虑不同 Windows 版本之间的结构差异,比如部分版本的 DestList 条目长度可能扩展,解析时要基于可用的流长度动态判断。

C# JumplistWindows跳转列表用户行为分析修改时间:2026-09-23 01:48:42

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0923/60705.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。