Library / SDK
yincongcyincong/wechat_chatter avatar
yincongcyincong/wechat_chatter

wechat_chatter: driving WeChat on macOS from a Frida hook

微信(wechat hook macos) mac 收发消息逆向

657 stars323 forksJavaScriptGPL-3.0

At a glance

What is it?
A research codebase that hooks the lowest level send capability inside WeChat for macOS and exposes it to Frida scripts, with a onebot HTTP interface for the result. It targets the current WeChat build only, requires SIP to be disabled or a Frida Gadget to be injected, and the project states it is for study and technical exchange.
Who is it for?
Adopt wechat_chatter if you are studying how the WeChat macOS client constructs and dispatches messages, or if you want the onebot HTTP interface and are willing to maintain the hook against each client update. Do not adopt it if you need a supported messaging integration, because the code targets one WeChat build at a time, requires disabling SIP or injecting a Gadget into a signed app, and carries the legal exposure of reverse engineering a proprietary client.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 25 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the project hooks, and why it goes that deep

The repository description is short and blunt: 微信(wechat hook macos) mac 收发消息逆向, which is to say a macOS WeChat message send and receive hook built from reversing the client. The readme explains the choice of target, and it is not the user interface. The author hooked the lowest level send capability of the WeChat client across the three platforms, and notes that the underlying code is open source and points to tencent/mars for anyone interested in how it works.

Hooking the send capability that deep is a deliberate move. A hook at the UI level would have to simulate clicks and keyboard input and would be fragile in a different way. A hook at the transport level is closer to what the client itself does, which means the message that comes out has been through the same encoding and dispatch path as a message a person typed.

The price of that choice is that the hook is tied to internals of a particular client build. The readme states plainly that only the latest WeChat version is supported, that historical versions are in the release section, and that the releases no longer ship buf fixes. A project like this has a shelf life measured in client releases.

The project states its own terms of use: all code, examples, documentation and related content are for study, research and technical exchange, and any risk from using it, including data loss, system crashes, security problems and legal risk, is borne by the user. That disclaimer is worth reading as a design statement as well as a legal one, because the code path involves deliberate memory manipulation inside another running application.

The four hooks, and the crash they were designed to avoid

The readme walks through four hooks, and the reasons given for each are the most instructive part of the documentation.

Hook 1 is the trigger function. Invoking it has the same effect as the user pressing enter, because it triggers startTask. That is the entry point: you call it with a message id, a wxid and the text.

Hook 2 injects a message body into the Req2Buf function. The author is explicit about why this is not enough: at hook 1 no message body has actually been supplied, and the injected body failed throughout the protobuf handling, because everything was pointers and could not be understood. So hook 3 injects the protobuf content directly and then sends.

Hook 4 is the one that prevents a crash. It clears the message body in Req2Buf, because the memory is reclaimed later in OnTaskEnd. If the message body is still attached to the whole pointer when that happens, the memory is cleared, but the thread does not recognise that memory, and the whole program crashes.

That sequence is a compact description of what reverse engineering a message pipeline actually involves. The first hook finds the right entry point, the second and third fight with a serialisation layer that is built around pointers and a shared buffer, and the fourth deals with lifetime rules that were never meant to be violated. The existence of hook 4 is a good indicator of how little the original design intended for this to be driven from outside.

The repository layout reflects the same split between research and tooling. There is a diaphora directory for diffing, idapro and docs directories, idapro.md and function.md as notes, mars.md pointing at the open source transport code, a frida directory with the scripts, a frida-gadget directory for the injected path, a onebot directory, a tools directory, a wechat_version directory, a todo_list, and the build scripts build.sh and setup_frida_and_build.sh.

Running the hook with SIP disabled

The usage path with System Integrity Protection turned off is the shorter one. Frida attaches to the WeChat binary at launch and loads the text script:

bash
# 只支持最新的微信版本,历史版本看release,并且release不再提供buf fix
frida -f /Applications/WeChat.app/Contents/MacOS/WeChat -l frida/text.js

Once the client is running, the message is sent by calling the trigger function from the Frida console with the message id, the recipient and the text:

js
triggerSendTextMessage(0x20000095, "wxid_xxxx", "hi")

The comment above the command is the author's own warning and belongs with the code: only the newest WeChat version is supported, older ones live in the release section, and the releases no longer provide buf fixes. The 0x20000095 value is the message id for a text send, which changes per client version, so it is a value you have to take from the current release rather than assume.

Note what this requires of your machine. Attaching a debugger to a signed platform binary means SIP has to be off, and the readme points at a separate document for the case where you have not turned it off, in the frida-gadget directory of the companion repository weixin-macos. The trade is described next.

The Frida Gadget path when SIP is still enabled

If SIP is on, the readme says to work through the linked frida-gadget readme step by step and then start WeChat. The command then attaches to the injected Gadget rather than to the WeChat process:

bash
# 只支持最新的微信版本,历史版本看release,并且release不再提供buf fix
frida -H 127.0.0.1:27042 -n Gadget -l ./frida/text.js

The differences are visible in the flags. There is no -f and no path to the application binary, because the process is already running and the script is being loaded into the Gadget that was injected into it. The -H 127.0.0.1:27042 argument points at a host and port, and -n Gadget selects the process by name. The script path is relative, ./frida/text.js, which means the working directory matters when you run it.

The trigger call is the same in both modes, since it is a function inside the client either way:

js
triggerSendTextMessage(0x20000095, "wxid_xxxx", "hi")

The choice between the two paths is a decision about your machine, not about the project. Disabling SIP is a global setting that weakens the protection macOS applies to signed system binaries. Injecting a Gadget means carrying a modified framework through a re-signed application, which the linked document walks through. Both are more invasive than a normal library integration, and the readme's own disclaimer about security risk and system crashes covers this part of the setup.

The repository also carries build.sh and setup_frida_and_build.sh, which indicate that the Gadget variant is packaged by the project rather than left entirely to the reader, though the readme here points to the companion repository for the step by step procedure.

Sending images needs the client to have sent one first

The image path in the readme has a requirement that is easy to miss: to send an image you first have to send one manually, so that the function can find the data in the X0 register.

The manual send also populates the client's temp directory, which the hook then uses. The directory structure follows the WeChat container layout:

bash
mkdir -p "/Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_xxx/temp/xxx/2026-01/Img/"
cp /Users/yincong/Desktop/1.png /Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_xxx/temp/xxx/2026-01/Img/xxx.jpg

After that, two calls are needed, one to upload the image and one to send the message:

js
triggerUploadImg("wxid_7wd1ece99f7i21", "8dd4755e12e052fa5647a883e6bf0783", "/Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_xxx/temp/xxx/2026-01/Img/xxx.jpg")
triggerSendImgMessage(0x20000199, "wxid_xxx","wxid_xxx")

The upload call takes a recipient, a hash and the path on disk, and the send call takes the message id 0x20000199 and two wxid values. The image path embeds a month directory, 2026-01, which means the path has to be recreated for the period the client is in, and it carries a hash value that the upload step returns.

The requirement to send an image manually first is the interesting part. The function locates the data through a register that only holds it in the course of a real send, so the hook cannot bootstrap the image path from nothing. Any automation around this has to include a manual priming step, or a different entry point that this repository does not document.

The onebot HTTP interface, and where this stops being a demo

The readme states support for the onebot protocol over HTTP and links to the onebot readme in the companion repository weixin-macos for how to connect openclaw, and it points to a Telegram group for discussion. There is a onebot directory in this repository as well.

The onebot protocol is the convention Chinese chat bot frameworks use to expose a bot over HTTP, so supporting it turns the hook into something a bot platform can drive. The change in kind matters. A Frida script that you trigger by hand is a research instrument. An HTTP endpoint that accepts messages and pushes them into a client is a service, and services have operational consequences: something has to listen on a port, and whatever calls it can send as your account.

That is the boundary worth being clear about. The project itself does not advertise a bulk sending feature, and nothing in the documentation describes volume limits, rate handling or scheduling. The functionality it documents is the capability to send a message as the logged in account, and the onebot interface is the standard way to externalise that capability. Any deployment inherits the sending limits and the account risk of the underlying WeChat account, and the readme's disclaimer about legal risk and security problems is part of that inheritance.

The repository structure supports the reading that this is a working system rather than a proof of concept. There are separate directories for the Frida scripts, the Gadget variant, the onebot server, the tools, the version mapping and the reverse engineering notes, plus a todo_list that lists what is still to be done, and a wechat_version directory that pins the client build the code applies to.

What makes this fragile, and what it costs you

The fragility is structural rather than incidental, and it is worth spelling out before anyone builds on it.

The first constraint is version coupling. The hooks address internal functions in the WeChat binary, and the message id 0x20000095 for a text send and 0x20000199 for an image are per build values. When WeChat updates, the addresses move, the ids change, and the buffer layout the hooks assume can change with them. The project acknowledges this by shipping past releases per version and by a wechat_version directory, but it also says those releases no longer receive buf fixes, so the older branches are archaeology rather than supported options.

The second constraint is the machine setup. SIP disabled is a security posture change for the whole system, not a per project setting, and the Gadget path requires a re-signed application and a listening port. Neither is something you want on a machine that holds credentials for other things.

The third is memory safety inside a foreign process. The readme is unusually candid about this: the injected protobuf body failed because it was all pointers, and the cleanup hook exists because memory reclaimed later in OnTaskEnd would otherwise be cleared while still attached, which crashes the program. Even with those four hooks in place, the code is operating on internal buffers whose ownership rules are undocumented.

The fourth is the licence. The project is GPL-3.0, so if you distribute a binary containing this code, or offer it as a service, the copyleft terms reach your own code. The readme is explicit that use is for study, research and technical exchange, which is a narrower statement than what a commercial bot deployment would need.

The fifth is the legal question, which the project puts on the user. Reverse engineering a proprietary client and driving its internals sits in a legally contested area in several jurisdictions, and the readme says so directly. That is not something a licence file can resolve.

Editorial conclusion

Adopt wechat_chatter if you are studying how the WeChat macOS client constructs and dispatches messages, or if you want the onebot HTTP interface and are willing to maintain the hook against each client update. Do not adopt it if you need a supported messaging integration, because the code targets one WeChat build at a time, requires disabling SIP or injecting a Gadget into a signed app, and carries the legal exposure of reverse engineering a proprietary client. Verify first that your WeChat version matches the version listed under wechat_version and the release notes, and read the LICENSE file, since GPL-3.0 obligations apply to code you distribute or link into a service.

Frequently asked questions

What does wechat_chatter do and what is it used for?

It is a macOS WeChat message hook built from reversing the client, hooking the lowest level send capability so messages can be triggered from a Frida script. The project states that all code is for study, research and technical exchange, and that any resulting risk is borne by the user.

How do I run wechat_chatter on macOS?

With SIP disabled, run frida -f /Applications/WeChat.app/Contents/MacOS/WeChat -l frida/text.js and then call triggerSendTextMessage with a message id, a wxid and the text. If SIP is enabled, follow the linked frida-gadget document and attach with frida -H 127.0.0.1:27042 -n Gadget -l ./frida/text.js instead.

Does wechat_chatter support the onebot protocol?

Yes. The readme states support for the onebot protocol over HTTP and links to a onebot document in the companion weixin-macos repository for how to connect openclaw, and the onebot directory is part of this repository.

How do I send an image message with wechat_chatter?

An image must be sent manually once first, so the function can find the data in the X0 register, and the image copied into the client temp directory under xwechat_files. Then triggerUploadImg is called with the recipient, a hash and the path, followed by triggerSendImgMessage with the message id 0x20000199.

Which WeChat versions does wechat_chatter support and what licence does it use?

Only the latest WeChat version is supported, with historical versions kept in the release section and no buf fixes provided for them, and the wechat_version directory maps builds to code. The project is GPL-3.0, the repository is not archived, the last push was on 2026-09-28, and recent releases include v0.0.18 on 2026-07-10 and v0.0.17 on 2026-07-06.

Official sources

  1. Issues
  2. License: GPL-3.0
  3. README
  4. Releases
  5. yincongcyincong/wechat_chatter on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yincongcyincong-wechat-chatter.svg)](https://hysenlabs.com/projects/yincongcyincong-wechat-chatter)

Was this useful?