我是怎样一步一步实现微信自动抢红包的?
在逆向工程中,我们可以使用静态分析和动态调试的方法去寻找我们的目标函数!本文我将分享一下逆向微信实现微信自动抢红包的实战经验,越狱,非越狱机器 都可适用,项目 GitHub 地址: RHWeChat。
本项目在以下环境下测试运行:
- 非越狱 iOS10.3.2,iPhone 6设备
- 开发环境:Xcode 8.3.2 , MonkeyDev
简单说下环境搭建:
- 安装最新的 theos:命令行式的开发 Tweak 的编译环境
sudo git clone --recursive https://github.com/theos/theos.git /opt/theos
- 安装 ldid:签名工具
- 自己编译
- 使用 brew 安装, 有点慢!
// 自己编译
sudo mv ldidpath /opt/theos/bin
sudo chmod 777 /opt/theos/bin/ldid
// brew 安装
brew install ldid
- 安装 MonkeyDev:基于 theos 实现的Xcode的开发环境。
git clone https://github.com/AloneMonkey/MonkeyDev.git
cd MonkeyDev/bin
sudo ./md-install
关于MonkeyDev详情,请参见:MonkeyDev的文档
大家踊跃给猴神 star 啊!
猴神在他的 MonkeyDev 中,默认集成了Reveal.framework,Cycript.framework,class-dump;并且在MonkeyDev中,你不需要手动提取ipa中的二进制文件, 修改二进制文件的Load Commands列表,加入要hook的dylib ,hook.dylib在函数constructor函数中完成对特定函数的hook,对修改后的ipa进行重签名,打包和安装,等一系列复杂的过程!Command+R 一键搞定,若要生成 ipa 文件只需在 Command+R 运行之后在源代码的 LatestBuild 目录双击createIPA.command 生成。
逆向思路:
要实现自动抢红包的功能,我们首先应该知道一个手动抢红包的流程!
所以我们先分析手动抢红包实现:
- 首先我们从 UI 入手
使用 reveal 找到打开红包的按钮,获取内存地址,通过 UIControl 的以下方法获取target和action
- (NSSet *)allTargets; // set may include NSNull to indicate at least one nil target
- (UIControlEvents)allControlEvents;
- (nullable NSArray<NSString *> *)actionsForTarget:(nullable id)target forControlEvent:(UIControlEvents)controlEvent; // single event. returns NSArray of NSString selector names. returns nil if none
image.png
- 打开 Hopper Disassembler,载入经过解密的微信可执行文件,搜索得到的 target 和 action,分别对应:WCRedEnvelopesReceiveHomeView 的 OnOpenRedEnvelopes 方法!
- 由汇编代码得到他的 delegate 调用了
WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes 方法 - 我们继续在 class-dump 得到的头文件中搜索关键字WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes 方法
- 我们找到了 WCRedEnvelopesReceiveControlLogic 类,实现了该代理方法,所以我们继续打开 Hopper Disassembler 搜索WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes 方法
- 可以大致分析出拆开一个红包大致需要 msgtype,sendid,channelid,nickName,headImg,nativeUrl,sessionUserName,timingIdentifier,这些参数,最后调用 OpenRedEnvelopesRequest: 打开红包
- 以上是看汇编代码大致得出的结论,具体还要结合测试验证,我们 hook OpenRedEnvelopesRequest:方法
CHDeclareClass(WCRedEnvelopesLogicMgr)
CHOptimizedMethod1(self, void, WCRedEnvelopesLogicMgr, ReceiverQueryRedEnvelopesRequest, id, arg1) {
NSLog(@"%@", arg1);
CHSuper1(WCRedEnvelopesLogicMgr, ReceiverQueryRedEnvelopesRequest, arg1);
}
- 运行程序打上断点,运行打开红包流程,看会不会进断点,进了断点看下打印内容!果真如我们所预料的一样,进了参数打印如下:
{
channelId = 1;
headImg = "http://wx.qlogo.cn/mmhead/ver_1/tUsGXv3VM2C4f6Nj1vibaWy2jJUFPMnUfobCtP1iajd5QLCn8B7YM6L6E1UTwSzxkiaichIJuVibBx2BRiaAIu1UkYfjMnJiatYxpvlWdHdfBD8xKU/132";
msgType = 1;
nativeUrl = "wxpay://c2cbizmessagehandler/hongbao/receivehongbao?msgtype=1&channelid=1&sendid=1000039501201709137014370817311&sendusername=wxid_mryox0wndn8122&ver=6&sign=8f446c219788bb6db911ff7de1eefd3ac9a66298b8d8d9be3f0018f14524bbb16ca78e0b22357967ebeb36860b892b0d5300d8ae5ae70ac29759a3c673cea9f6205adb65ae01d71d29a7b7a2b757333c0f264b07f893dfb88c28ece6e9869236";
nickName = "xxxx";
sendId = xxx;
sessionUserName = "xxxxx";
timingIdentifier = 88BF75742FFAB59D7F88C494670C3FE3;
}
-
接下来我们要仔细分析一下每个参数如何获取:
-
nickName,headImg通过 CContactMgr 的 getSelfContact 获取到 selfContact,selfContact的m_nsHeadImgUrl和getContactDisplayName属性分别对应headImg,nickName
image.png -
msgType 为常数1
image.png -
nativeUrl,应该是截取红包 url 的一段字符串,通过上面的打印参数可以验证!
image.png - sessionUserName,没太看懂,应该是发红包人的名字,因为上文有个 nickName 我看懂了是 selfContact,自己的名称!
-
sendid,channelid,timingIdentifier,通过 objectForKey: 获取,上文字典的获取只有通过 WCBizUtil 的 dictionaryWithDecodedComponets:separator: 获取
image.png
-
-
所以我们接着进入 dictionaryWithDecodedComponets:separator: 函数的汇编代码分析
-
参数我没有分析出来是什么类型的,我们Hook dictionaryWithDecodedComponets:separator: 方法
%hook WCBizUtil
+ (id)dictionaryWithDecodedComponets:(id)arg1 separator:(id)arg2 {
%log;
return %orig;
}
%end
�[m +[<WCBizUtil: 0x103b28eb8> dictionaryWithDecodedComponets:msgtype=1&channelid=1&sendid=xxxxxxxxxxxxx&sendusername= xxx-com&ver=6&sign=f4455577adc87b21387127f45e6c3803649800302ac90134018c10c4a87b3a0a5df85d4360028ad28b997bbdb4e52e6b8b62804fc04444185bb4f6617359a8c41565d1592d5d251c1a8b0aaef1fdb3de576d88890323720ae701bb3e56b3e900 separator:&]
- 他的参数应该是红包 url 的 get 参数,我们使用 & 解析出来然后我们可以得到以下参数 sendid,channelid,sessionUserName!
- 至此 我们得到了除 timingIdentifier 之外的所有参数
- 但是这个 timingIdentifier 怎么解决呢?
- 可以分析出来的是从 m_structDicRedEnvelopesBaseInfo �获取到的,在 class-dump 出来的头文件搜索 ,发现这个字段在WCRedEnvelopesControlData中,但是怎么获取,我本人没有从汇编代码中研究出 timingIdentifier 的来源!
- google 了一下 timingIdentifier 应该是微信加的一个防止机器人抢红包的字段,怎么解决呢?
-
猜!既然不在点击打开红包按钮的时候获取,会不会在点击红包进入打开红包页面的时候获取呢?
继续从 UI 入手
image.png - 首先从[BaseMsgContentViewController tableView:didSelectRowAtIndexPath:] 的汇编代码来看,没有找到类似的请求!
- 那就从 Cell 本身去看,在class-dump 的头文件中,搜索 WCPayC2CMessageCellView本身没有类似方法
-
继续找它的父类WCPayBaseMessageCellView,onTouchUpInside方法有可能是我们的目标
image.png -
查看onTouchUpInside汇编代码
image.png - 调用了 tapAppNodeView方法 搜索tapAppNodeView,发现在BaseMsgContentViewController中,我们继续回到BaseMsgContentViewController
-
查看BaseMsgContentViewController的tapAppNodeView方法的汇编代码:
image.png
image.png - 调用了[WCRedEnvelopesControlMgr startReceiveRedEnvelopesLogicByC2C:Data:] 查看汇编代码没有找到!
-
继续查看 tapAppNodeView 汇编代码发现最后调用了
[WCRedEnvelopesControlMgr startReceiveRedEnvelopesLogic:Data:]方法
image.png - 查看汇编代码,我们发现 startReceiveRedEnvelopesLogic:Data 调用了WCRedEnvelopesReceiveControlLogic的startLogic 方法
-
于是我们又回到了 WCRedEnvelopesReceiveControlLogic中,使用 Hopper 打开WCRedEnvelopesReceiveControlLogic 的 startLogic实现
image.png
image.png -
我们发现显示为灰色,完全看不懂,查了一下Hopper 的官方文档发现灰色代表undefined,不知道为什么!这个问题想了很久没想通!但是在搜索关键字的时候有一个极其相似的方法:
[WCRedEnvelopesGreetingReceiveControlLogic startLogic]
然后我就想这两个会不会实现类似,于是我分析了该函数发现
image.png
image.png - 以上调用了 WCRedEnvelopesLogicMgr的ReceiverQueryRedEnvelopesRequest
方法,接下来就是验证看有没有调用这个方法,hook 该方法,打上断点
CHDeclareClass(WCRedEnvelopesLogicMgr);
CHOptimizedMethod1(self, void, WCRedEnvelopesLogicMgr, ReceiverQueryRedEnvelopesRequest, id, arg1) {
NSLog(@"%@", arg1);
/*
agreeDuty = 0;
channelId = 1;
inWay = 1;
msgType = 1;
nativeUrl = "wxpay://c2cbizmessagehandler/hongbao/receivehongbao?msgtype=1&channelid=1&sendid=1000039501201709137007742337288&sendusername=jirenbang-com&ver=6&sign=f4455577adc87b21387127f45e6c3803649800302ac90134018c10c4a87b3a0a5df85d4360028ad28b997bbdb4e52e6b8b62804fc04444185bb4f6617359a8c41565d1592d5d251c1a8b0aaef1fdb3de576d88890323720ae701bb3e56b3e900";
sendId = 1000039501201709137007742337288;
*/
CHSuper1(WCRedEnvelopesLogicMgr, ReceiverQueryRedEnvelopesRequest, arg1);
}
- 点击红包消息,发现触发了断点,打印参数同上面打开红包获取的一样,接下来我们测试一下ReceiverQueryRedEnvelopesRequest:方法的参数
-
查看汇编代码
image.png - 没看懂,只能又靠猜,这里肯定是发了一个网络请求,肯定会有响应!我们查看一下WCRedEnvelopesLogicMgr的头文件搜索 response发现以下OnWCToHongbaoCommonResponse: Request:方法有点像,我们测试一下该方法:
CHOptimizedMethod2(self, void, WCRedEnvelopesLogicMgr, OnWCToHongbaoCommonResponse, id, arg1, Request, id, arg2) {
NSLog(@"%@", arg1);
NSLog(@"%@", arg2);
/*
<HongBaoRes: 0x1123400f0>
<HongBaoReq: 0x1123805f0>
*/
CHSuper2(WCRedEnvelopesLogicMgr, OnWCToHongbaoCommonResponse, arg1, Request, arg2);
}
- 我们搜索打开HongBaoRes.h文件,查看发现了一个可疑的retText属性类型为SKBuiltinBuffer_t
@property(retain, nonatomic) SKBuiltinBuffer_t *retText; //
- 我们继续搜索SKBuiltinBuffer_t.h 文件,我们又发现
@property(retain, nonatomic) NSData *buffer; // @dynamic buffer;
- 一般我们的网络数据返回都是 json 格式,我们不妨写代码测试一下
if ([NSStringFromClass([arg1 class]) isEqualToString:@"HongBaoRes"]) {
NSData *data = [[arg1 retText] buffer];
if (nil != data && 0 < [data length]) {
NSError* error = nil;
id jsonObj = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:&error];
}
}
- 经调试,如我们所猜测的一样,我们的 jsonObj 中含有timingIdentifier字段!
- 到此,我们完整的分析出了手动领取一个红包的目标函数,所要的所有参数,和所有的参数是如何获取的!
接下来我们就应该着手实现自动抢红包的逻辑!
- 不用多想,自动抢红包的时机,肯定是在收到消息的时候咯!所以我们去 hook 收到消息的方法!
- 接下来我们分析消息,对于这种没有 UI 的方法,我的逆向思路就是猜!关键字搜索是我用来最常用的猜测方法,消息英文 message,缩写msg!
-
我们在 class-dump 头文件中搜索 message
image.png - 上图红框部分这几个最像了,点击一个一个查看!最终定位到CMessageMgr的方法最像了,CMessage,CMessageWrap更像是模型,CMessageMgr是管理消息的地方!
-
我们打开CMessageMgr.h文件,搜索关键字 receive没有,搜索 message,也没有,搜索 msg
image.png - 这些画红圈是我认为比较可以可疑的方法,不过我觉的最可疑的还是
- (void)AsyncOnAddMsg:(id)arg1 MsgWrap:(id)arg2;
- 这个方法,原因很简单,首先即有 msg,又有 msgwrap,然后一般这种多参数的方法,按照我写代码的思路来说,参数越多说明这个方法越受约束,越不不通用,这样想的话我首先 hook AsyncOnAddMsg:MsgWrap:方法!
- 接下来就是写代码验证了:
CHDeclareClass(CMessageMgr);
CHMethod(2, void, CMessageMgr, AsyncOnAddMsg, id, arg1, MsgWrap, id, arg2) {
NSLog(@"%@", arg1);
NSLog(@"%@", arg2);
CHSuper(2, CMessageMgr, AsyncOnAddMsg, arg1, MsgWrap, arg2);
}
-
叫人发个红包给我们,触发断点,查看参数:
image.png - 上图我们需要关注的是 m_uiMessageType 和 m_nsContent
- m_uiMessageType == 49 为 红包消息
- m_nsContent 如下图
<msg>
<appmsg appid="" sdkver="">
<des><![CDATA[我给你发了一个红包,赶紧去拆!]]></des>
<url><![CDATA[https://wxapp.tenpay.com/mmpayhb/wxhb_personalreceive?showwxpaytitle=1&msgtype=1&channelid=1&sendid=1000039501201709137014370817311&ver=6&sign=8f446c219788bb6db911ff7de1eefd3ac9a66298b8d8d9be3f0018f14524bbb16ca78e0b22357967ebeb36860b892b0d5300d8ae5ae70ac29759a3c673cea9f6205adb65ae01d71d29a7b7a2b757333c0f264b07f893dfb88c28ece6e9869236]]></url>
<type><![CDATA[2001]]></type>
<title><![CDATA[微信红包]]></title>
<thumburl><![CDATA[http://wx.gtimg.com/hongbao/1701/hb.png]]></thumburl>
<wcpayinfo>
<templateid><![CDATA[7a2a165d31da7fce6dd77e05c300028a]]></templateid>
<url><![CDATA[https://wxapp.tenpay.com/mmpayhb/wxhb_personalreceive?showwxpaytitle=1&msgtype=1&channelid=1&sendid=1000039501201709137014370817311&ver=6&sign=8f446c219788bb6db911ff7de1eefd3ac9a66298b8d8d9be3f0018f14524bbb16ca78e0b22357967ebeb36860b892b0d5300d8ae5ae70ac29759a3c673cea9f6205adb65ae01d71d29a7b7a2b757333c0f264b07f893dfb88c28ece6e9869236]]></url>
<iconurl><![CDATA[http://wx.gtimg.com/hongbao/1701/hb.png]]></iconurl>
<receivertitle><![CDATA[Best wishes]]></receivertitle>
<sendertitle><![CDATA[Best wishes]]></sendertitle>
<scenetext><![CDATA[微信红包]]></scenetext>
<senderdes><![CDATA[查看红包]]></senderdes>
<receiverdes><![CDATA[领取红包]]></receiverdes>
<nativeurl><![CDATA[wxpay://c2cbizmessagehandler/hongbao/receivehongbao?msgtype=1&channelid=1&sendid=1000039501201709137014370817311&sendusername=wxid_mryox0wndn8122&ver=6&sign=8f446c219788bb6db911ff7de1eefd3ac9a66298b8d8d9be3f0018f14524bbb16ca78e0b22357967ebeb36860b892b0d5300d8ae5ae70ac29759a3c673cea9f6205adb65ae01d71d29a7b7a2b757333c0f264b07f893dfb88c28ece6e9869236]]></nativeurl>
<sceneid><![CDATA[1002]]></sceneid>
<innertype><![CDATA[0]]></innertype>
<paymsgid><![CDATA[1000039501201709137014370817311]]></paymsgid>
<scenetext>微信红包</scenetext>
<locallogoicon><![CDATA[c2c_hongbao_icon_cn]]></locallogoicon>
<invalidtime><![CDATA[1505373603]]></invalidtime>
</wcpayinfo>
</appmsg>
<fromusername><![CDATA[wxid_mryox0wndn8122]]></fromusername>
</msg>
-
对比刚开始的 dictionaryWithDecodedComponets:separator:方法传入的参数就是我们的 url 的一部分!
-
我们可以通过 url 获取到msgType,sendId,channelId
-
至于nickName ,headImg我们可以从前面的汇编代码还原出实现来
-
nativeUrl截取 url 的一部分
-
sessionUserName可以从 arg2 中获得
-
至此我们已经获取到了打开红包除了 timingIdentifier 外的所有参数
-
timingIdentifier参数,我们调用前面分析出来的 [WCRedEnvelopesLogicMgr ReceiverQueryRedEnvelopesRequest:] 方法来获取
-
该方法需要以下参数都可以通过 m_nsContent 获取到
agreeDuty = 0;
channelId = 1;
inWay = 1;
msgType = 1;
nativeUrl = "wxpay://c2cbizmessagehandler/hongbao/receivehongbao?msgtype=1&channelid=1&sendid=1000039501201709137007742337288&sendusername=jirenbang-com&ver=6&sign=f4455577adc87b21387127f45e6c3803649800302ac90134018c10c4a87b3a0a5df85d4360028ad28b997bbdb4e52e6b8b62804fc04444185bb4f6617359a8c41565d1592d5d251c1a8b0aaef1fdb3de576d88890323720ae701bb3e56b3e900";
sendId = 1000039501201709137007742337288;
- 之后我们 hook WCRedEnvelopesLogicMgr 的
OnWCToHongbaoCommonResponse方法,通过arg1取到 timingIdentifier字段! - 最终我们打开红包的所有参数获取完毕,接下来只需要调用OpenRedEnvelopesRequest:方法,便可以实现自动抢红包功能!
- 至此,我们成功实现了微信自动抢红包的功能!
总结:
在逆向抢红包的过程中,我主要使用了静态分析和动态调试的手段:
- 静态分析,主要是从 class-dump 出来的头文件,辅助 Reveal,cycript,来寻找目标类或者目标函数,涉及到具体逻辑的时候,我会使用 Hopper Disassembler 来查看具体方法的具体实现,来进一步的猜测实现逻辑
- 动态调试,我们静态分析出来的结果或猜测,需要动态的去调试验证!
- 由于我是未越狱设备,我主要使用的是 Xcode + MonkeyDev 来 Hook 原方法,然后打印参数等简单的调试
- 如果你是越狱设备你可以使用 lldb+debugserver 神器来调试你的程序,那样的话理论上整个程序对你来说都是透明,因为你可以像在 Xcode正向开发应用一样具体一步一步的调试,对那些感兴趣的功能点,你可以下断点,然后去触发断点,跟踪调试!
最后分享一下我的逆向技巧:
- 猜 + 验证,用关键字去猜,想想如果是自己写这个 App 会怎样去写,会怎样命名!
- 然后你可以使用 Finder 的搜索功能,说句题外话,Finder 的搜索功能确实好用不仅可以搜索到文件名,内容也可以搜索到!
- 当然你也可以使用 Xcode 新建一个工程将 class-dump 出来的头文件添加到工程,利用 Xcode 的搜索shift+cmd+o 搜索文件,shift+cmd+f,全局搜索关键字!
- 一般我都是使用常用的命名去搜,比如在寻找微信消息收发目标函数的时候,我们没有 UI 辅助,这时候只能靠猜,我使用了 message msg receive 等关键字查找定位
- 在获取 timingIdentifier 参数的时候我不知道 我通过分析找到了
ReceiverQueryRedEnvelopesRequest方法来获取该参数,但是它的实现我看不太懂,这时候我又使用了猜,搜索了 response,找到了
ReceiverQueryRedEnvelopesResponse 方法!最终获取到了timingIdentifier 参数! - 当然猜的过程中你可能会出错,这是一个非常枯燥的过程,逆向工程相对于正向开发来说是一件比较枯燥的事情,可能你花了几天最终得到的只是一个目标函数!而在正向开发过程中,你写的代码都会转换为成果!或许是一个完美的动画,或许是流畅的界面!
- 所以看完如果你能够完整看完本文,然后一步一步去实践的话,那说明你还是比较适合干这个的,如果不能那还是老老实实搞点 app 开发吧!
本人是个逆向新手,若有错误之处,请多多指正
参考文章:
iOS 应用逆向工程(入门书籍)
iOS冰与火之歌 – UAF and Kernel Pwn - 蒸米
MonkeyDev 文档