C# 操作Windows Timeline文件 C#如何读取ActivitiesCache.db来分析用户活动历史

1次阅读

activitiescache.db 是经 windows dpapi 加密的 sqlite 数据库,需先调用 cryptunprotectdata 解密为明文字节流,再通过内存或临时文件方式加载;解密必须在当前已登录用户上下文执行,payload 和 visualelements 字段为 utf-8 json 二进制数据,时间戳为 filetime 格式。

C# 操作Windows Timeline文件 C#如何读取ActivitiesCache.db来分析用户活动历史

ActivitiesCache.db 是加密的 sqlite 数据库,直接用 System.Data.SQLite 打不开

windows Timeline 的活动数据存放在 %LocalAppData%Packagesmicrosoft.Windows.ShellExperienceHost_8wekyb3d8bbweLocalStateActivitiesCache.db,但它不是普通 SQLite 文件——微软用 Windows DPAPI 加密了整个数据库文件。你用常规方式打开会得到乱码或“file is encrypted or is not a database”错误。

实操建议:

  • 必须先调用 CryptUnprotectData(通过 P/Invoke)解密数据库内容到内存或临时文件,再交给 SQLite 驱动读取
  • 不能直接用 SQLiteConnection 打开原始路径,否则报错 SQL logic Error: file is encrypted or is not a database
  • 解密需以当前用户上下文运行(不能在服务或 SYSTEM 下执行),且用户必须已登录并解锁过系统(DPAPI 密钥绑定会话)

解密后表结构简单,但关键字段是 Activity 表里的 payloadvisualElements

解密后的数据库里,Activity 表存所有活动记录,但主体数据藏在二进制字段中:其中 payload 是序列化的 JSON(UTF-8 编码),包含应用 ID、启动时间、用户操作类型;visualElements 是另一段 JSON,含标题、描述、图标 URI 等展示信息。

实操建议:

  • 别尝试用 DataTable.Load() 直接读 payload 字段——它返回的是 byte[],需手动转 Encoding.UTF8.GetString() 后再 JsonSerializer.Deserialize
  • startTimeendTime 字段是 Windows FILETIME(100-nanosecond intervals since 1601-01-01),不是 unix 时间戳,转换用 DateTime.FromFileTimeUtc()
  • 某些活动(如 edge 浏览记录)的 payload 可能为空,优先看 visualElements 里的 displayText

权限和路径访问失败常见于 UWP 沙箱限制和路径硬编码

即使你是管理员,C# 程序默认无法访问 Microsoft.Windows.ShellExperienceHost_* 包下的 LocalState 目录,因为这是 UWP 应用专属路径,受 Package Identity 和 Capability 限制。

实操建议:

  • 不要硬写完整路径,用 Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) 拼接,并确保程序不是以“兼容模式”或“低完整性级别”运行
  • 如果提示“拒绝访问”,检查是否启用了 Windows Defender Controlled Folder access——它会拦截对 LocalState 的读取
  • 开发调试时,可先用 PowerShell 手动复制一份解密后的 ActivitiesCache.db 到桌面再读,绕过权限问题

C# 解密 + 读取要分两步走,缺一不可

没有现成的 .NET 类封装这个流程。你得自己调 CryptUnprotectData,再喂给 SqliteConnection。跳过解密或跳过 UTF-8 解码都会导致解析失败。

简短示例逻辑:

// 先读加密文件 byte[] encrypted = File.ReadAllBytes(dbPath); // 解密(注意:pDataIn 是加密数据,pDataOut 是输出缓冲区) if (CryptUnprotectData(..., out byte[] decrypted)) {     // 写入临时文件或用 MemoryStream 构造 SQLite 连接字符串     using var conn = new SqliteConnection($"Data Source=:memory:");     // ...加载 decrypted 到内存 DB 或写临时文件再连 }

容易被忽略的一点:DPAPI 解密返回的 decrypted 是完整 SQLite 文件字节流,必须原样写入磁盘或传给支持内存数据库的驱动(如 Microsoft.Data.Sqlite 的 DataSource=:memory: + LoadExtension 不行,得用 SqliteConnection + SqliteCommand 手动导入)。

text=ZqhQzanResources