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

# Racing Against WeChat Versions: The Adaptation History of a Local Tool

Turning WeChat on macOS into a local interface for AI agents is not hardest the first time it works; the hard part is keeping it alive after WeChat updates again and again. This covers three generations of methods for obtaining the database key, how background message sending changed its routing approach, and why what truly carries across versions is the method for finding things, not any specific address.

Jun 12, 2026 · Posts · Public · Article

ON THIS PAGE

We built a tool that turns your own WeChat on macOS into a local interface for AI agents / scripts: sending messages, searching conversations, and reading history, all on your Mac, without going to the cloud or using the iPad protocol.

The day it first worked felt pretty satisfying. Then WeChat updated.

Chasing WeChat on the version treadmill

Versions Are Always Running Ahead

The tool works by directly reading WeChat's local database and locating several key functions and fields in memory. The problem is that where all these "things" are depends entirely on the specific version: every time WeChat's minor version number bumps up (4.1.8 → 4.1.9 → 4.1.10), the compiled binary may move a function somewhere else or change the offset of a struct field. Ordinary users never notice. For us, it breaks on the spot.

So the daily work on this project is not "building new features"; it is chasing. The little person on the treadmill is me, and the green chat bubble is always just out of reach.

Every Update Breaks It, Often Silently

A little robot falling apart after an update

What makes it even more exciting is silent hot updates: WeChat can replace its core component in the background—the dynamic library that carries all its logic—with a new version, without showing any dialog. Everything is fine in the morning, then your script starts throwing errors in the afternoon, and you have no idea the binary has already been swapped. Every investigation starts with the same question: "Did it hot-update again?"

That forced the first thing we did to be turning off its automatic hot updates: lock down the update path so the current adaptation does not get secretly knocked out overnight, buying a window where the tool can work stably.

Getting the Key: Three Generations, Forced by WeChat

A key that does not fit the keyhole

WeChat's local databases are encrypted with SQLCipher. To read the data, we first need to obtain the 32-byte raw key. We went through three generations of methods here, each forced by a new version:

First generation — scanning heap memory for the key text. When SQLCipher decrypts a database, it executes a statement similar to PRAGMA key = "x'……'". In earlier versions, this hexadecimal key text remained in the process heap. By directly scanning process memory and matching that shape, we could fish out the key. This still worked in 4.1.9.

Second generation — scanning binary keys + verifying with the page header HMAC. Once WeChat stopped leaving the key as plaintext text in the heap, the first generation was dead. So we fell back to a more brute-force approach: treat every 32-byte segment in the heap as a candidate key, then verify it against the HMAC of the database's first page. SQLCipher carries HMAC verification on each page; the wrong key will not pass. The problem was that by 4.1.10, after scanning hundreds of MB of heap and millions of candidates, the number of HMAC hits was zero—the plaintext key was simply no longer in the heap. And one scan took tens of minutes, far too slow to feel usable.

Third generation — placing a breakpoint on the system encryption function. The key shift in thinking was this: even if the key is not in the heap, it must still "flow through" somewhere. Under the hood, SQLCipher uses macOS's system CommonCrypto. Every time it decrypts a database, it calls CCCryptorCreate to create an AES context, and this system function's symbol has not been stripped. Put a breakpoint on it, and when the key length is 32, the pointer in the arguments points to the key. One catch, one hit.

4.1.10 also changed the key model while it was at it: from "one master key" to one key per database. So we have to make WeChat decrypt each database, so the breakpoint can catch all the keys.

To summarize this image in one sentence: with every major version jump, the key gets hidden in a new way, and the old method always falls just a little short of the keyhole.

Background Message Sending: Changing “Who This Goes To”

Sending messages is much harder than reading data. What we want is to send a message completely in the background, without stealing your mouse focus. That inevitably runs into one question: how does WeChat know which conversation this message should enter?

There is a field in memory that records "who the current message is going to." The idea behind background sending is to stop at a breakpoint somewhere in the sending path and rewrite this routing field to our target, letting the downstream logic deliver the message according to the new value. This worked across several versions.

In 4.1.10, we got stuck, and the way it got stuck was quite educational: following the signatures, we found a field that "looked exactly like the routing wxid," modified it—and the message still went into the original conversation. Later we understood why: that field was a written copy filled in by some setter, not the actual point downstream reads from to decide the route. Change the copy, and the source remains the same. We still have not found the real routing read point.

What Really Carries Across Is the Method, Not the Location

After chasing a string of versions, the most valuable lesson is this:

Every "specific address, offset, and version fingerprint" you write down will basically be invalid in the next version. The only thing that survives across versions is the method for finding things.

Roughly speaking, here is what can be reused and what is guaranteed to expire:

  • Guaranteed to expire: hard-coded function addresses, field offsets, binary fingerprints—one reshuffle by Tencent and they are all obsolete.
  • Reusable: using distinctive instruction sequences to locate functions (for example, a function body contains a uniquely distinctive instruction that appears only once in the entire binary, and that lets us find the same function again in the new version); using function size + call-chain topology as a fingerprint (Tencent's hot updates often only move locations without changing function bodies, so size and shape can still line up across versions); and structural lessons—like the earlier point that "the setter copy is not the real routing point," which remains useful knowledge.

So the real moat is not the result of any single crack; it is the accumulated craft of finding things. Results expire. The craft does not.

Where Things Stand Now

Halfway up the mountain: reading works, sending has not crossed over yet

An honest update for the latest 4.1.10:

  • Reading works — using the third-generation key retrieval method, the CommonCrypto breakpoint, conversations, history, and contacts can all be fetched normally.
  • Sending has not crossed over yet — it is stuck at the step described above: "the real routing read point has not been found yet."

The flag with the check mark halfway up the mountain in the image is the current state; the red X at the summit is the part we are still climbing toward. No pretending that "everything is done"—when you build an adaptation this close to the internal structure of someone else's app, there is always another version and another peak ahead.

Closing Notes

The positioning has never changed: local-first, no cloud, with data fully in your own hands. It is a member of the *-use series (alongside profile-use and iphone-use), all of which let agents directly operate your real local tools instead of moving data onto someone else's servers.

The code, installation method, and capability boundaries are all in the repository: github.com/leeguooooo/wechat-use. For personal / research use only—commercial use, bulk messaging, or automating other people's accounts are all outside the scope of support.

The version chase continues. See you at the next peak.

next →
iphone-use — Computer-use, but for the iPhone

Comments

Replies are public immediately and may be moderated for policy violations.

Max 1000 characters.

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