~/field-notes — leeguoo@misonote — zsh EN ● 中文 日本語
❯ field-notes v3.4.1
leeguoo@misonote:/zh/posts/chasing-wechat-versions/ $ 文章

# 和微信版本赛跑:一个本地工具的适配史

把 macOS 上的微信变成给 AI agent 用的本地接口,最难的不是第一次跑通,是让它在微信一次次更新后还活着。聊聊取数据库 key 的三代办法、后台发消息怎么改路由、以及为什么真正能跨版本复用的是"找东西的方法"而不是某个具体地址。

2026年6月12日 · 文章 · 公开 · 文章

本页目录

我们做了个工具,把 macOS 上你自己的微信变成给 AI agent / 脚本用的本地接口:发消息、查会话、读历史,全在你这台 Mac 上,不上云、不走 iPad 协议。

第一次跑通那天挺有成就感。然后微信更新了。

在版本跑步机上追微信

版本永远在前面跑

工具的原理,是直接读微信的本地数据库、并在内存里定位到几个关键函数和字段。问题是这些"东西在哪"全都依赖具体版本:微信每次小版本号往上跳(4.1.8 → 4.1.9 → 4.1.10),编译出来的二进制就可能把函数挪个位置、把结构体字段换个偏移。对普通用户毫无感知,对我们就是当场失灵。

所以这个项目的日常不是"做新功能",是。跑步机上那个小人就是本人,绿气泡永远在够不着的前面。

一更新就崩,而且常常是悄悄崩

一更新就散架的小机器人

更刺激的是静默热更:微信会在后台把核心组件(那个承载全部逻辑的动态库)换成新版,不弹任何对话框。你早上还好好的,下午脚本就开始报错,而你根本不知道二进制已经被换了。每次排查都得先确认一句:"是不是又热更了?"

这逼着我们做的第一件事,反而是关掉它的自动热更——锁住更新路径,让当前这套适配别被半夜偷偷打掉,争取一个能稳定干活的窗口。

取 key:三代办法,被微信逼着升级

对不上锁孔的钥匙

微信的本地库是 SQLCipher 加密的,要读数据就得先拿到那把 32 字节的原始密钥。这一块我们换了三代办法,每一代都是被新版本逼出来的:

第一代——扫堆里的密钥文本。 SQLCipher 解库时会执行类似 PRAGMA key = "x'……'" 的语句,早期版本里这串十六进制密钥文本会残留在进程堆里。直接扫进程内存、匹配这个形状,就能把 key 捞出来。4.1.9 还能用。

第二代——扫二进制密钥 + 用页头 HMAC 验证。 等微信不再把密钥以明文文本留在堆里之后,第一代就废了。退而求其次:把堆里每一段 32 字节都当候选密钥,拿数据库第一页的 HMAC 去验(SQLCipher 每页带 HMAC 校验,密钥不对就过不了)。能验过的就是真 key。问题是到 4.1.10,扫遍几百 MB 堆、几百万个候选,HMAC 命中数是——明文密钥根本不在堆里了。而且扫一遍要几十分钟,慢到不像个能用的办法。

第三代——断点打在系统加密函数上。 关键转念:密钥不在堆里,但它一定会"流过"某个地方。SQLCipher 底层用的是 macOS 系统的 CommonCrypto,每解一个库都要调 CCCryptorCreate 建 AES 上下文,而这个系统函数的符号没被剥掉。把断点下在它身上,当密钥长度是 32 时,参数里那个指针指向的就是 key。一抓一个准。

4.1.10 还顺手把密钥模型也改了:从"一把主密钥"变成每个库一把(per-DB key)。所以得让微信把各个库都解密一遍,断点才能把钥匙抓全。

一句话总结这张图:每翻一个大版本,钥匙就换一种藏法,旧办法凑到锁孔前总是差那么一点。

后台发消息:得改"这条发给谁"

发消息比读数据难得多。我们要的是完全后台、不抢你鼠标焦点地把一条消息发出去。这就绕不开一个问题:微信怎么知道这条消息该进哪个会话?

内存里有一个字段,记着"当前这条要发给谁"。后台发送的思路,是在发送链路的某个断点处,把这个路由字段改写成我们的目标,让下游照着新值把消息投出去。在好几个版本上,这招是通的。

到 4.1.10 卡住了,而且卡得很有教育意义:我们顺着特征找到一个"看起来就是路由 wxid"的字段,改它——结果消息照样进了原来那个会话。后来才搞明白,那个字段是个被写入的副本(某个 setter 往里填的),根本不是下游真正读来决定路由的那个点。改副本,源头照旧。真正的路由读取点还没找到。

真正能复用的,是方法不是位置

追了一串版本之后,最值钱的一条经验是:

你记下来的所有"具体地址、偏移、版本指纹",下个版本基本全废。能跨版本活下来的,只有"怎么把东西找出来"的方法。

具体哪些能复用、哪些必废,大概是这样:

  • 必废:硬编码的函数地址、字段偏移、二进制指纹——腾讯重排一次全过期。
  • 能复用:用特征指令序列去定位函数(比如某个函数体里有一条很独特的指令,全二进制只此一处,靠它能在新版本里重新找到这个函数);用函数大小 + 调用链拓扑做指纹(腾讯热更往往只搬位置、不改函数体,所以大小和形状能跨版本对上);以及结构性的教训——像上面那个"setter 副本不是真路由点",是会一直管用的认知。

所以真正的护城河不是某一次破解的结果,是攒下来的一套找东西的手艺。结果会过期,手艺不会。

现在爬到哪了

半山腰:读通了,发还没翻过去

诚实交代到最新的 4.1.10:

  • 读通了——靠第三代取 key 办法(断点 CommonCrypto),会话、历史、联系人都能正常取。
  • 发还没翻过去——卡在上面那个"真正的路由读取点还没找到"的环节。

图里插旗打勾的半山腰就是现状,山顶那个红叉是还在爬的部分。不装"全部搞定"——做这种贴着别人应用内部结构的适配,永远有下一个版本、下一个山头。

写在最后

定位一直没变:本地优先、不上云、数据完全在你自己手里。它是 *-use 系列的一员(还有 profile-use、iphone-use),都是让 agent 直接操作你本地的真实工具,而不是把数据搬到别人的服务器上。

代码、装法、能力边界都在仓库里:github.com/leeguooooo/wechat-use。仅供个人 / 研究用途——商业、群发、自动化别人的账号一律不在支持范围内。

追版本这事还在继续。下一个山头见。

下一篇 →
iphone-use — 把 computer-use 搬到 iPhone 上

评论

评论发布后会立即公开,如触发规则可能被审核下架。

最多 1000 字。

    ⎇ main ● 0 errors · 0 warnings UTF-8 Markdown /zh/posts/chasing-wechat-versions/ © 2026 leeguoo