简介:这份资源是一份面向越狱苹果设备的微信插件包,主要提供自动抢红包与消息防撤回两大功能,既可直接安装使用,也适合作为iOS逆向工程与Tweak开发的参考实例。压缩包内共整合30个文件,包含5个deb安装包、2个dylib动态库、5个plist配置列表、8张效果截图,以及Xcode工程、Tweak源码(.xm、.mm、.h)、说明文档和编译配置等,便于用户快速部署插件或深入分析实现逻辑。资源整体仅817KB,体量轻巧,适合直接在越狱设备上加载测试。目前已有520人学习下载,作者为lijunhcn,压缩包目录结构清晰。通过阅读源码与配置,读者可以掌握微信抢红包的检测与触发流程、防撤回的实现思路,同时熟悉Theos或MonkeyDev环境下插件的编写、编译与安装方法;此外还能根据自身需求调整参数、重新打包,是一份兼顾实用和研究价值的资料包。
1. iOS微信自动抢红包和防撤回插件:这份资源包里到底有什么
把这套 iOS微信自动抢红包和防撤回插件.zip 解压之后,你会看到一堆 .xm、.plist、Makefile 和一些编译好的 .deb。它不是什么开箱即用的成品 App,而是一整套 iOS 端插件的工程源码与产物:一份负责监听红包消息、自动拆红包的 Tweak,一份负责拦截消息撤回逻辑的 Tweak。适合两类人:一类是刚入门 iOS 逆向,想知道红包消息在微信内部怎么流转的开发者;另一类是手里正好有对应版本越狱设备,想让手速不再输给外挂的普通用户。我先说结论:自动抢红包的难点不在“抢”,而在识别消息和防取消;防撤回的难点不在“拦”,而在消息缓存怎么续命。这篇笔记把这两块拆开讲,每个 hook 点都会给到实际可抄的参数和边界。
2. 环境搭建:越狱与非越狱两条路线的实测差异
插件代码写得再好,环境不对就是白搭。微信插件基本只有两条路:越狱环境下用 Theos 编译 deb 往 MobileSubstrate 里塞;非越狱环境下用 MonkeyDev 做 dylib 注入重签名。两条路我都跑过,先说结论:调试期用越狱路线省事,分发自己用用选非越狱;如果你想长期跟版本更新,越狱路线维护成本反而低。
2.1 越狱路线:Theos 构建 deb 的完整流程
Theos 是一套跨平台的越狱插件构建工具链,核心价值是把“改方法实现”这件事简化成类似面向切面的 Logos 语法。你在 .xm 文件里写%hook到%end,它会在编译期帮你生成 swizzle 代码,省掉一堆 objc_msgSend 的手工拼接。
第一步,先把工具链装好。这里以 macOS 为例,Terminal 里执行:
export THEOS=~/theos git clone --recursive https://github.com/theos/theos.git $THEOS # 国内网络建议加代理,超时重试即可THEOS环境变量必须指向 Theos 根目录,编译时它靠这个变量找 makefile 模板和 SDK。--recursive必须带,Theos 依赖多个子模块,漏了后面编译会报找不到libhbang之类的错。装完后验证一下:
$THEOS/bin/nic.plnic.pl是工程生成器,会问你创建哪种工程类型,选iphone/tweak,然后依次填工程名、包名、作者名、依赖版本。这里注意:包名建议直接用反域名格式,比如com.yourname.wechattweak,后面 Control 文件里要一致,不然安装时会提示冲突。
第二步,改 Makefile。生成出来的 Makefile 默认长这样:
TARGET := iphone:clang:latest:7.0 TWEAK_NAME = WeChatTweak WeChatTweak_FILES = Tweak.xm WeChatTweak_CFLAGS = -fobjc-arcTARGET里末尾的7.0是 iOS 最低部署版本,微信现在都要求 iOS 12+,建议改成11.0或更高,避免编译期对旧版 SDK 的特判。WeChatTweak_CFLAGS加-fobjc-arc是因为我们要在 hook 方法里持有对象,不开 ARC 的话 block 和 NSString 的内存管理会翻车。
第三步,写control文件。这是 deb 包的元信息,决定 Cydia 怎么识别它:
Package: com.yourname.wechattweak Name: WeChat Tweak Version: 1.0.0-1 Architecture: iphoneos-arm Depends: mobilesubstrate (>= 0.9.5000)Depends必须声明依赖mobilesubstrate,它是 MobileSubstrate(即 Cydia Substrate)的运行库。很多新人装完插件没生效,十有八九是设备上根本没装这个依赖。越狱工具现在默认带,但如果清理过可以重新装一次。
第四步,编译打包:
make clean && make packagemake package会调用 dpkg-deb 把工程打包成 deb。生成的 deb 在packages/目录下,文件名一般是com.yourname.wechattweak_1.0.0-1_iphoneos-arm.deb。传到手机后有两种安装方式:拷贝到设备上用dpkg -i安装,或者直接用scp推到设备再执行安装。装完记得respring重启桌面,插件才会被 MobileSubstrate 加载进微信进程。
2.2 非越狱路线:MonkeyDev 注入与重签名
非越狱路线的核心思路是:先拿到微信的 ipa 包(自己用工具砸壳),塞一个 dylib 进去,再用你的开发者证书重签名,最后装到没越狱的手机上。MonkeyDev 这套 Xcode 插件把“注入 dylib + 重签名”做成了工程模板,省掉手动改 Mach-O 的步骤。
用 MonkeyDev 建工程时选MonkeyDevTweak模板,然后把砸过壳的WeChat.app直接拖进工程目录,它会自动识别并生成注入逻辑。需要在 Build Settings 里确认两处:
CODE_SIGNING_ALLOWED = NO // 先关掉自动签名,避免和后续脚本冲突 MONKEYDEV_DEVICE_IP = 192.168.x.x // 真机调试时才需要编译选真机 Device,Xcode 会跑一段 post-build 脚本,自动把libWeChatTweak.dylib注入到 Mach-O 的 Load Commands 里,然后整体重签名。这里Bundle ID建议改成一个不冲突的,比如com.yourname.wechattweak,直接沿用原包名的话,如果设备上装了官方微信,会直接安装失败。
非越狱方案的坑在于:每次微信发版你都要重新砸壳、重新注入、重新签名,一次流程走下来大概二十分钟。越狱方案换版本只需要重新编译 deb,然后dpkg -i覆盖安装。我的习惯是主设备用越狱版本,备用机走 MonkeyDev 做调试验证。
3. 自动抢红包核心:从消息模型到异步拆包的三处关键 hook
自动抢红包不是简单的“检测到红包就点”,微信红包的触发链路分三段:消息到达 → 识别红包类型 → 调用打开红包接口。每一段都有对应的 hook 点。三段都做对,才能做到“消息弹出来之前就已经拆完了”。
3.1 红包消息识别:抓住 type 49 的 App 消息并取到 payload
微信的红包消息属于 App 消息的一种。在消息模型中,这类消息对象带appMsg字段,内部有type属性,红包类型的 type 值是一个固定标识(常见值是 49 外层包装下的子类型),同时还有wcRedEnvelope之类的标记。不同微信版本字段名略有差异,所以在 hook 之前,要先用class-dump把当前版本的头文件导出来,确认具体类名和属性名。
第一步,写一个针对消息接收类的 hook,拦截新消息回调:
%hook CMesssageMgr - (void)onNewMsg:(id)msg { %orig; // 1. 只处理 App 消息 if ([[msg valueForKey:@"type"] intValue] != 49) { return; } // 2. 取 App 消息内部数据 id appMsg = [msg valueForKey:@"appMsg"]; if (appMsg == nil) { return; } // 3. 判断是否为红包类型 int appMsgType = [[appMsg valueForKey:@"type"] intValue]; if (appMsgType == RED_ENVELOPE_TYPE) { // 4. 通知拆包模块处理 [[WeChatRedEnvelope shared] handleMessage:msg appMsg:appMsg]; } } %end这段代码的%orig不管你是否要拦截都必须保留,它负责调用原始方法。如果漏掉%orig,微信的收消息逻辑会直接断掉,表现为消息收不到、会话不刷新。判断红包类型这里有个细节:RED_ENVELOPE_TYPE是写在头文件里的宏定义,不同版本可能不一样。我只建议用常量去比对,不要用字符串isRedEnvelope这种 KVC 拼写,因为版本一升级 key 名变了,编译期又不会报错,运行时一直走不到分支,红包自然不触发。
3.2 自动拆包:从手动点击变成后台异步执行
识别到红包只是第一步。正常用户拆红包,是要走一遍 UIAlertView 的确认弹窗,再走打开红包接口。自动抢的核心就是绕过这个确认弹窗,直接调用打开红包的逻辑,同时加一个延时队列防止操作太快被系统判为异常。
%hook WCRedEnvelopeLogic // 当红包消息到达后,主动触发打开逻辑 - (void)OnOpenRedEnvelope:(id)msg { // 延时执行,模拟真人操作,避免被风控 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 这里直接调用打开红包的原生方法 %orig; }); } // 拦截弹窗,让它不出现 - (void)startShowRedEnvelopeAlert:(id)alert { // 不调用 %orig,弹窗直接被吞掉 } %end第二段startShowRedEnvelopeAlert不调用%orig是刻意的:这个方法是用来弹出“查看红包”提示的,吞掉它就是让用户无感知。但这里有个边界要讲清楚:吞掉弹窗同时也会吞掉红包消息的下发链路,部分版本中如果你完全不调用%orig,可能导致红包被打开后消息界面不刷新。我实测过两种写法,稳妥的做法是在不调用%orig的同时,手动刷一下当前会话列表。添加延迟 0.2 秒不是拍脑袋,是微信有风控逻辑,同一毫秒内连续操作多个红包会被识别成脚本,延迟分散开反而更安全。
3.3 过滤策略与参数边界
自动抢不等于全抢。群里抢到别人的定向红包、口令红包、或者你自己发出的红包,都是事故。我一般会在插件里维护一张过滤表,字段如下:
| 参数 | 类型 | 说明 |
|---|---|---|
| groupID | NSString | 群聊 ID 白名单,为空则抢所有群 |
| excludeGroupIDs | NSSet | 黑名单群,设置后白名单失效 |
| keywordFilter | NSArray | 群口令关键词,不匹配则不抢 |
| maxAmount | double | 最大金额,超过不抢(可选) |
| delaySeconds | double | 拆包延时,默认 0.2~0.5 |
| maxCountPerMinute | int | 每分钟最大拆包数,防止短时间动作过多 |
过滤逻辑放在handleMessage:里,抢之前先比对当前场景。举个例子,如果群里发了一个“专属红包”,appMsg里会带 recipient 字段,这时候要用 getUserName 跟当前登录账号比对,不一致直接 return。很多插件翻车就翻在只做了关键词过滤,没做领取人判断,把专属红包也抢了,场面非常尴尬。
4. 防撤回实现:拦截撤回指令后消息为什么还能留在会话里
防撤回听起来比抢红包简单——把撤回方法 hook 掉不就行了吗?实际上没那么轻松。微信的撤回机制涉及两层:服务端下发的撤回指令,和客户端对本地消息列表的变更。只拦截指令调用,消息气泡可能依然会被移除,因为另一段代码在做列表清理。
4.1 撤回调用链与消息缓存
先理一下撤回在客户端的表现链路:对方撤回消息后,服务端向所有接收方推送一条撤回指令;客户端收到指令后,会执行两个动作,一是调本地消息管理类的撤回方法,二是把消息列表里对应的原消息内容替换成“某某撤回了一条消息”的占位气泡。
问题在于:占位气泡的生成可能是由撤回方法自己完成的,也可能由一个独立的reload流程触发。不同版本实现不同,所以防撤回插件不能只 hook 一个点,要把“撤回处理”和“消息刷新”两处都覆盖到。
%hook MMMessageMgr // 撤回指令的统一入口 - (void)onRevokeMsg:(id)msg { // 拿到被撤回的原始消息 id originalMsg = [msg valueForKey:@"originalMsg"]; if (originalMsg) { // 把原消息缓存到一个内存字典里 [[WeChatAntiRevoke shared] cacheMessage:originalMsg]; } // 不调用 %orig,撤回指令直接被吞掉 } // 消息列表刷新时,检查是否有被撤回的消息需要恢复 - (void)reloadMessageList { %orig; [[WeChatAntiRevoke shared] resumeCachedMessages]; } %end这里最关键的决策是%orig的取舍。如果撤回流程必须要调原始方法才能保 UI 稳定,你就得调;但如果调了,消息列表会被刷新为占位文案。我实测下来,大部分版本中onRevokeMsg这个入口就是撤回的终结者,直接吞掉它,后续 UI 不会出现“撤回了一条消息”,原气泡保持为正常消息。如果遇到吞掉后消息仍然消失,那就说明列表刷新是另一条链路触发的,需要在reload时恢复缓存。
4.2 本地回放:把缓存的消息重新写回会话列表
如果缓存和恢复没有做对,就会出现“防了撤回但消息没了”的诡异效果。恢复流程的原理是:缓存被撤回消息的原始模型对象,在会话列表刷新完成后,再次调用消息数据源方法把对象插回列表。
- (void)resumeCachedMessages { NSArray *cached = [self allCachedMessages]; for (MessageItem *item in cached) { // 重新插入会话列表,并刷新 UI [[objc_getClass("MessageListViewController") sharedInstance] insertMessage:item]; } [self clearCachedMessages]; }插入之后要刷新 UI,否则缓存恢复了但显示不出来,需要下拉一下才出现。insertMessage:和reloadMessageList中间要隔一个 runloop 循环,给列表一个重新渲染的时机。直接同一线程连续插入,容易出现列表计数错乱,表现为会话里消息数量翻倍,部分气泡重叠。另一个边界:缓存的 MessageItem 持有图片、文件之类的大对象,建议在插入完成后立即释放缓存,不然微信长时间挂后台,内存会被撤回消息的缓存拖垮。
4.3 防撤回的限制范围
需要给新手指个方向:防撤回只能防“接收方本地”的撤回展示,它没有能力阻止服务端删除消息。也就是说,如果对方撤回后,你打开另一台没装插件的设备看同一会话,消息还是被撤回的;如果服务端在推送撤回指令时已经清理了内容,你缓存里拿到的 originalMsg 也有可能是空对象。这不是插件 bug,是架构边界。把预期管理好,测试时就不会误判成插件失效。
5. 避坑与常见问题:闪退、失效、不触发的五个现场
插件开发过程中我踩过的坑,十个有八个都集中在环境匹配和 hook 点变更上。下面按“现象 → 原因 → 解决”的格式记录,方便你排查时直接对照。
5.1 安装插件后微信一打开就闪退
现象:安装 deb 并 respring 后,点击微信图标,闪退;syslog 里能看到libWeChatTweak.dylib加载失败的记录。
原因:插件里 hook 了一个当前微信版本不存在的方法。iOS 运行时对class_addMethod这类动态修改很敏感,hook 目标方法找不到时,MobileSubstrate 会直接抛异常,把整个进程带崩。
解决:先查当前微信版本的类头文件,确认%hook里的方法名跟class-dump导出的完全一致。常见错误是大小写不对、参数类型不匹配、或者方法名多了个冒号。把Tweak.xm里对应段落临时注释掉再编译,确认闪退是否消失,用二分法定位是哪个 hook 点引起的。
5.2 重新签名安装后提示应用已损坏
现象:MonkeyDev 编译产物装到非越狱设备,打开提示 yellow 屏幕或“无法验证应用完整性”。
原因:重签名时证书和描述文件不匹配。MonkeyDev 默认可能用了-通配符证书,但设备上安装的描述文件不含对应 Bundle ID 的权限。
解决:在 Xcode 的 Signing & Capabilities 里把 Team 设置为自己的开发者账号,描述文件用包含com.yourname.wechattweak的 App ID。确认 build 完成后用codesign -dv检查签名链:
codesign -dv /path/to/WeChat.app输出里 Look forSignature字段,确认Authority是 iPhone Developer,而不是 iPhone Distribution,后者用于发布,真机调试装不上。
5.3 红包消息到了但不触发自动抢
现象:群里发红包后,插件没有任何反应,红包最终被别人抢完。
原因:这个场景涉及两个可能:一是红包消息到达时,消息对象里type字段已经不是 49 了;二是你的 hook 点选在了onNewMsg:,但当前会话在后台时,微信走的是推送通道,根本不经过这个回调。
解决:区分两个入口。前台消息走onNewMsg:,后台推送红包走PushNotify相关的回调。两处都要 hook,并且在识别逻辑里打印日志确认到底进了哪个分支。代码里加一行:
NSLog(@"[WeChatTweak] msg type=%d appMsgType=%d", type, appMsgType);通过 Xcode 设备日志或idevicesyslog看输出,判断是没进回调还是类型判定失败。
5.4 某些群红包被抢了,但列表里没显示已抢状态
现象:插件自动拆了红包,但会话里红包气泡仍显示“待领取”,刷新后才变成“已拆开”。
原因:自动流程跳过了原生的拆包动画和列表刷新步骤,只调了核心打开方法,缺少后续的状态同步。
解决:拆包完成后,手动触发一次列表刷新。在打开红包成功回调里调用refreshCurrentMessageList,或者把insertMessage:跟当前时间戳一起刷新。这里建议加一个 0.3 秒的延时再刷新,避免拆包和刷新并发争抢数据库锁。
5.5 防撤回在单聊里正常,在群聊里失效
现象:单聊里撤回的消息能保留;群聊里对方撤回后,消息要么消失,要么变成占位文案。
原因:单聊和群聊走的是两套撤回指令,入口方法不同。单聊撤回可能是onRevokeMsg:,群聊撤回则可能是onRevokeGroupMsg:或者在同一个方法里带 group 参数。只 hook 一个入口,群聊链路自然全覆盖不了。
解决:用 class-dump 同时搜索revoke和Revoke两个关键字,把所有相关方法都列出来,逐个确认参数里带没带 group id。把 group 入口一并 hook,并复用同一套缓存逻辑:
%hook MMMessageMgr - (void)onRevokeGroupMsg:(id)msg { // 同样进入缓存流程,不调 %orig [[WeChatAntiRevoke shared] cacheMessage:msg]; } %end6. 验证插件是否生效:装进设备前的四步检查法
系统重装、iOS 升级、微信版本更新之后,插件不生效太常见了。我以前是直接装上乱试,翻车好几次之后,现在固定走四步验证,每次都管用。
第一步,验 hook 点存在。拿当前版本的微信头文件,搜你在代码里写的每个方法名。grep onRevokeMsg -r搜不到,说明版本更新把方法改名了,直接放弃这个 hook 点。
第二步,验注入生效。装完插件后不急着点红包,先在%init里加一行日志:
%ctor { NSLog(@"[WeChatTweak] loaded"); }微信启动后看 syslog 有没有这行输出。没有,说明 dylib 压根没被加载,查签名和安装路径;有,说明注入链路没问题,继续往下查逻辑。
第三步,验触发链路。先用小号给主力机发一个 0.01 元的红包,看日志里有没有打印出 type 值和 appMsgType 值。这一步能区分是“识别逻辑没走”还是“识别到了但打开失败”。
第四步,验防撤回边界。用小号发一条带表情的普通消息,撤回复查。这里一定要同时测单聊和群聊,光测单聊会漏掉 5.5 里的群聊坑。测试通过后再把插件用于日常环境。
从那以后,我每次切换微信版本,都强制走一遍这四步,耗时十分钟,省掉的是装完发现没效果再逐段排查的几个小时。这套流程对任何 iOS 端 Tweak 调试都通用,不只是这两个功能。如果这份资源包里的插件正好覆盖了你需要的场景,先把环境按第 2 章搭好,再对照第 5 章的踩坑记录逐条核对,能少走不少弯路。希望帮到你。
本文还有配套的精品资源,点击获取