前言

两个月前,我遇到了一个需求:把微信里的聊天记录导出来。

市面上的方案要么过时了,要么只支持旧版本。微信 4.1.x 的架构发生了很大变化,之前的工具基本都不能用了。于是我决定自己动手,从零开始逆向分析微信 Windows 版的聊天消息存储机制。

这篇文章记录了从一片空白到做出完整导出工具的整个过程,既是心得总结,也算是一份技术教程。

故事的起点

最初我面临的问题很简单:微信的聊天记录存在哪里?

传统认知里,聊天记录应该存在数据库里。微信早期版本确实如此——用 SQLite 存消息。但 4.1.x 版本之后,情况完全不同了。

第一阶段:地毯式搜索

第一周我干了件非常笨的事——全盘搜索。把电脑上所有文件按修改时间排序,逐个检查微信相关目录下的每一个文件。结果一无所获。

接着上 ProcMon(文件操作监控工具),过滤微信进程,设置几十个文件路径规则,监控微信的所有文件读写操作。翻页、搜索、切换会话……观察到的文件访问全都是配置类的,没有消息数据。

结论 1:微信 4.1.x 的聊天消息不在磁盘上。

这个发现让我很意外。消息不落地,意味着它们只存在于内存中。

第二阶段:攻入进程内存

既然在内存里,那就直接读内存。

使用 Python 的 pymem 库附加到 Weixin.exe 进程,扫描堆内存。通过消息特征的字节模式(02 05 09 01 01 04)找到了大量匹配。然后验证:翻页后刷新匹配结果,数量和内容都变了——确认消息确实在进程堆内存中,而且是实时变化的。

首次”导出”就拿到了 57 条消息,虽然有很多乱码和噪声,但方向对了。

当时的导出方式非常简单粗暴:全堆扫描特征字节 → 提取附近文本 → 拼成 JSON。

打开 Ghidra:从黑盒到白盒

内存扫描虽然可行,但很不优雅。我决定用 Ghidra(NSA 开源的逆向工具)反编译 Weixin.dll,理解消息系统的内部结构。

Weixin.dll 有 175MB,Ghidra 分析就跑了接近两小时。但结果是值得的。

找到 GetPagedMessages

通过搜索字符串特征,找到了关键函数 GetPagedMessages。从名字看,这就是微信分页加载消息的核心函数。

在 4.1.9.56 版本中它位于偏移 0x016ade70,但微信升级到 4.1.10.29 后函数地址变了。我需要在新版本中重新定位它。

最终在 Ghidra 中找到了新地址,并构建了它的 Call Tree——这个函数内部调用了 28 个子函数,把它们的功能一个个梳理清楚:

  • 3 个函数负责构建查询参数(拼接 JSON:orientation、sort_order、types、sender……)
  • 1 个函数负责以步长 0x2d8 遍历消息数组
  • 1 个函数是 CheckMessageLiveStatus(消息状态检查)
  • 多个函数负责哈希表缓存管理

关键突破:0x2d8 消息结构和 Creator

在分析过程中发现,所有消息操作都以步长 0x2d8(728 字节) 访问数组。这说明微信内部用固定大小的结构体管理消息,每个消息节点恰好 728 字节。

通过分析字段拷贝函数(FUN_181482400),还原出了消息结构体的字段布局:

偏移 字段 说明
+0x000 vtable 虚表指针
+0x120 receiver 聊天对象 ID
+0x268 content_ptr 消息内容指针
+0x288 content_ptr2 备用内容指针

然后问了那个终极问题:这 728 字节的消息节点是谁创建的?

花了将近一周,沿着调用链向上追溯:GetPagedMessages → GetMessageListBySvrIds → 消息管理器 → 网络调度器……最终在 Weixin.dll 的一个角落里找到了 FUN_181bc3b00

反编译确认,这个函数内部调用了 6 次 alloc(0x2d8),分配后用默认模板初始化,然后插入消息容器。这就是 MessageNode 的工厂函数。

从逆向到产品

理解了消息机制后,开发导出工具就是工程问题了。

工具的技术路线经历了三个阶段:

阶段一:内存扫描(M1-M38)

直接扫描 Weixin.exe 进程堆,通过特征字节定位消息,原地提取文本。优点是简单直接,缺点是数据不完整、准确率只有 95%。

阶段二:Ghidra 驱动 Hook(M39-M50)

利用逆向得到的函数地址,用 Frida 做动态 Hook,在消息到达时实时捕获。准确率更高,但操作复杂。

阶段三:数据库解密(M73-M112)

最终方案——微信的聊天消息虽然在内存中处理,但本地有一个加密的数据库副本,包含全部聊天记录(包括离线消息)。

通过 Frida Hook 捕获微信进程中的解密密钥,然后用 WCDB(微信自研数据库引擎)直接读取解密后的数据库,拿到完整的消息数据、会话列表、联系人信息。

这就是最终工具的核心原理。

成果

项目历时近两个月,累计 100+ 轮实验,最终产出两个 GitHub 仓库:

研究仓库

包含完整的研究过程、Ghidra 分析报告、实验脚本、反编译数据、数据库解密方案等。适合对技术细节感兴趣的读者。

🔗 github.com/Ray0612/WeChat-v4-export-research

导出工具

支持一键获取解密密钥、连接数据库、浏览会话、导出聊天记录。开箱即用,无需配置。

🔗 github.com/Ray0612/WeChat-Export-Tool

心得

复盘这轮开发,几点体会:

  1. 逆向工程是耐心活 —— 一周没有任何进展是常态。一个函数需要反复对比、验证、推翻重来。Ghidra 的分析经常是几十个函数互相调用,需要耐心梳理。

  2. 静态分析 + 动态验证是黄金组合 —— Ghidra 反编译给出”可能”的答案,Frida Hook 验证是否正确。光靠静态分析容易走偏,光靠动态调试又缺乏全局视野。

  3. 微信的消息架构设计得很巧妙 —— 全部在内存中处理,通过网络与服务器同步,本地只留加密缓存。这种设计兼顾了性能和安全,但也意味着客户端几乎不可能直接读消息。

  4. 社区的力量 —— 项目参考了大量前人成果(wx_key、weflow、WeChatMsg 等)。逆向工程站在巨人的肩膀上,每个工具都建立在前人的研究之上。

如果你对这个项目感兴趣,欢迎 star、fork、提 issue。如果有兴趣一起开发,也欢迎联系。