WeChatICU-ForMac源码解析(二):3行代码Method Swizzling与isRevoke防撤回Hook实现原理
【免费下载链接】WeChatICU-ForMacMac版企业微信消息防撤回, 聊天会话去水印项目地址: https://gitcode.com/gh_mirrors/we/WeChatICU-ForMac
WeChatICU-ForMac 是一个 Mac 企业微信消息防撤回、会话去水印的开源小工具。作为源码解析系列的第二篇,本文带你彻底看懂它最核心的实现:如何用3 行代码的 Method Swizzling,对isRevoke方法做一次 Hook,让消息"撤回"变成不可能。
一、项目回顾:WeChatICU-ForMac 到底能做什么?
这个项目的功能非常聚焦,主打两件事:
- 消息防撤回:同事撤回消息后,你的 Mac 企业微信上依然能看到原文,不再出现"XX撤回了一条消息"的提示;
- 会话去水印:聊天窗口背景不再显示你的姓名、企业名等信息。
感兴趣可以先克隆仓库体验一下:
git clone https://gitcode.com/gh_mirrors/we/WeChatICU-ForMac💡 整个项目的核心源码只有一个不到 50 行的 Objective-C 文件,编译产物是一个.dylib动态库。麻雀虽小,五脏俱全——它把 Objective-C Runtime 的两大"绝活"用到了极致。
二、消息撤回的真相:HookisRevoke为什么就够了?
要理解防撤回,先要知道企业微信的"撤回"是怎么工作的。
⚠️ 关键认知:撤回一条消息,服务端并不是把消息内容删掉,而是给它打上一个"已撤回"的标记。
消息在客户端以"消息模型对象"的形式存在,对象上有一个表示撤回状态的属性,通常就是一个isRevoke(是否已撤回)这样的只读方法:
- 消息正常 →
isRevoke返回NO - 对方撤回 →
isRevoke返回YES
聊天列表的 UI 层在渲染每一条消息时,会主动询问对象:"你被撤回了吗?"
- 答案是"是" → 界面隐藏原文,显示"XX撤回了一条消息"
- 答案是"否" → 界面照常显示原文
所以逻辑链条一目了然:
UI 渲染消息 → 调用 isRevoke() → 根据返回值决定显示方式如果我们能让isRevoke永远回答"否",UI 层就永远认为消息没被撤回——原文照常显示,撤回提示自然消失。防撤回的"终极开关",其实只在这一个方法的返回值上。
这正是 WeChatICU-ForMac 的思路:不去破解协议、不去拦截网络请求,只在 UI 询问的那一刻"篡改答案"。简单、优雅、副作用极小。
三、Method Swizzling 是什么?新手友好的解释
isRevoke是厂商代码,我们不能改它的源码,那怎么让它"永远返回 NO"?
这里就轮到 Objective-C Runtime 的经典技术登场了:Method Swizzling(方法交换)。
用一个生活化的比喻:
- 消息对象的
isRevoke方法,就像餐厅门口的招牌——别人喊的是招牌名字,但进去后吃到的,是招牌背后的厨房(实现); - Objective-C 的调用机制是"按名字查表找实现",而 Runtime 允许你在程序运行时修改这张"名字 → 实现"的映射表;
- Method Swizzling 就是:把
isRevoke这块招牌背后指向的厨房,偷偷换成我们自己的小作坊。
从此以后,任何代码调用isRevoke,拿到的都是我们"定制"的结果,而调用方对此毫无察觉。📌
四、3行代码核心:替换isRevoke的实现
下面就是 WeChatICU-ForMac 防撤回功能的核心(示意还原,实际项目代码极其精简):
第一步,写一个"假实现":
// 不管消息处于什么状态,永远告诉 UI:"我没被撤回" BOOL fake_isRevoke(id self, SEL _cmd) { return NO; }第二步,3 行代码完成 Swizzling:
// 第1行:找到消息模型类上的 isRevoke 方法 Method m = class_getInstanceMethod(msgClass, @selector(isRevoke)); // 第2行:准备"假实现"函数指针 IMP fake = (IMP)fake_isRevoke; // 第3行:把方法实现替换掉(核心就是这一行) method_setImplementation(m, fake);三行代码各司其职,非常清晰:
| 代码 | 作用 | 通俗理解 |
|---|---|---|
class_getInstanceMethod | 按类名 + 方法名,从 Runtime 查到方法记录 | 找到招牌 |
(IMP)fake_isRevoke | 把自己的函数取地址,转成方法实现类型 | 准备好新厨房 |
method_setImplementation | 直接覆盖该方法的实现 | 换掉招牌背后的厨房 |
📌一个常见疑问:教科书里 Swizzling 常用method_exchangeImplementations(交换两个方法的实现,便于原实现留作他用)。但防撤回场景根本不需要保留原逻辑——我们就是要"无条件返回 NO",所以项目用了更直白的method_setImplementation直接覆盖,一步到位。
小提示:
msgClass是消息模型类,类名需要通过逆向手段(如 class-dump 或动态调试)在企业微信进程中确认。不同企业微信版本的类名可能变化,这也是这类工具需要"跟版本"的原因。
五、如何让 Hook 自动生效?构造器 + 注入的"组合拳"
改完代码还有个现实问题:企业微信是厂商的应用,我们怎么让自己的代码跑进它的进程?
WeChatICU-ForMac 用了一套 macOS 上的经典组合拳,分两步:
第一步:利用 dylib 构造器,加载即自动执行 Hook
// 这个函数会在动态库被加载的瞬间自动执行,无需任何调用 __attribute__((constructor)) static void WeChatICUInit(void) { // 在这里执行上面那"3行代码" install_isRevoke_hook(); }__attribute__((constructor))是 Objective-C / C 的一个特性:被它标记的函数会在.dylib被加载进内存时自动调用。相当于给 Hook 装上了"开机自启"。
第二步:通过 DYLD_INSERT_LIBRARIES 把 dylib 注入企业微信进程
项目的启动脚本思路很直接(伪代码示意):
# 1. 退出正在运行的企业微信 killall WeCom # 2. 带上注入环境变量重新启动 DYLD_INSERT_LIBRARIES=/path/to/WeChatICU.dylib \ /Applications/WeCom.app/Contents/MacOS/WeCom &DYLD_INSERT_LIBRARIES是动态链接器(dyld)提供的环境变量:程序启动加载动态库时,会优先把指定的 dylib 注入进程。我们的 Hook 库就这样"搭便车"进入企业微信,构造器随即触发,3 行 Swizzling 完成,防撤回静默生效。✅
去水印功能也是同一套技术:Hook 水印绘制相关的绘制方法,让它跳过姓名、企业名称的绘制,界面背景瞬间"干净"。
六、总结:小项目里的大智慧
用一张表回顾本篇的知识点:
| 技术点 | 在项目中的角色 |
|---|---|
isRevoke方法 | 撤回状态的"总开关",UI 渲染的依据 |
| Method Swizzling | 运行时替换方法实现,实现无感 Hook |
method_setImplementation | 3 行代码中的关键一击,直接覆盖实现 |
__attribute__((constructor)) | dylib 加载时自动执行 Hook,免手动调用 |
DYLD_INSERT_LIBRARIES | 把 Hook 库注入企业微信进程 |
💡 这个项目值得新手反复品味的地方在于:它没有炫技,只是精确找到了"撤回提示"这条链路的最短切入点——一个布尔方法——然后用 Runtime 最基础的能力去改它。理解了isRevoke这一处,整个 macOS 逆向 Hook 的套路(找切入点 → 写假实现 → Swizzling → 注入进程)也就通了。
最后提醒:作者已在项目说明中表示 WeChatICU 停止维护,精力转向 WeChatExtension;且此类 Hook 依赖企业微信的具体内部实现,版本大更新后可能失效,使用前请留意兼容性与自身账号安全。⚠️
【免费下载链接】WeChatICU-ForMacMac版企业微信消息防撤回, 聊天会话去水印项目地址: https://gitcode.com/gh_mirrors/we/WeChatICU-ForMac
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考