本文仅供学习和参考,建议不用于商业用途,若用于商业用途后果自负
完整的工程地址
一 设置界面添加抢红包功能
在设置界面添加抢红包开关和延迟时间的两个cell
- 建立MonkeyApp工程,把微信ipa拖到TargetApp文件夹中,真机运行
-
用Xcode的Debug view找到设置界面的代码注入的地方
- 设置界面的控制器是:NewSettingViewController
控制器包含MMTableView 数据源是0x117964b60 - 利用Xcode的lldb调试:
(lldb) po 0x117964b60
delegate[0x11793f130], class[MMTableViewInfo]
- 在微信包内容中找到可执行文件,用class-dump导出头文件,用Sublime分析得到:
@interface MMTableViewInfo : MMTableViewUserInfo <UITableViewDelegate, UITableViewDataSource, tableViewDelegate>
{
MMTableView *_tableView;
NSMutableArray *_arrSections;
id <MMTableViewInfoDelegate> _delegate;
_Bool _disableAutoDeselectRows;
}
- (id)tableView:(id)arg1 cellForRowAtIndexPath:(id)arg2;
- (long long)tableView:(id)arg1 numberOfRowsInSection:(long long)arg2;
- (long long)numberOfSectionsInTableView:(id)arg1;
可以看出NewSettingViewController主要由MMTableViewInfo的MMTableView *_tableView构成
由此分析得到想在设置界面添加两个cell,要在MMTableViewInfo类中的tableView代理中设置
- 代码注入
%hook MMTableViewInfo
- (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)])//定位到设置界面
{
if([indexPath section] == [self numberOfSectionsInTableView:tableView]-1){
UITableViewCell * cell = nil;
if([indexPath row] == 0){
static NSString * switchCell = @"switchCell";
cell = [tableView dequeueReusableCellWithIdentifier:switchCell];
if(!cell){
cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:switchCell];
}
cell.textLabel.text = @"自动抢红包";
UISwitch * switchView = [[UISwitch alloc] init];
cell.accessoryView = switchView;
}else if([indexPath row] == 1){
static NSString * waitCell = @"waitCell";
cell = [tableView dequeueReusableCellWithIdentifier:waitCell];
if(!cell){
cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:waitCell];
}
cell.textLabel.text = @"等待时间(秒)";
UITextField * textField = [[UITextField alloc] initWithFrame:CGRectMake(0,0,150,40)];
textField.placeholder = @"等待时间";
textField.borderStyle = UITextBorderStyleRoundedRect;
cell.accessoryView = textField;
//监听键盘输入
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldDidChangeValue:) name:UITextFieldTextDidChangeNotification object:textField];
textField.text = [WCDefaults valueForKey:WCTIMEKEY];
}
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
return %orig;
}
return %orig;
}
- (long long)tableView:(UITableView *)arg1 numberOfRowsInSection:(NSInteger)section
{
if([arg1.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)])//定位到设置界面
{
NSMutableArray *arr = MSHookIvar<NSMutableArray *>(self,"_arrSections");
NSLog(@"--%@",arr);
if(section == [self numberOfSectionsInTableView:arg1]-1)
{NSLog(@"xxx--%@",arg1);
return 2;
}
return %orig;
}
return %orig;
}
- (long long)numberOfSectionsInTableView:(UITableView *)arg1
{
if([arg1.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)])//定位到设置界面
{
return %orig+1;
}
return %orig;
}
- (double)tableView:(UITableView *)tableView heightForRowAtIndexPath:(id)indexPath{
//定位设置界面&&最后一组
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)]
&& [indexPath section] == [self numberOfSectionsInTableView:tableView]-1){
return 44;
}
return %orig;
}
%end
- 本地数据缓存
-
图片直接拖到工程的Dylib文件夹中
- 数据直接NSUserDefaults 沙盒读写
#define WCDefaults [NSUserDefaults standardUserDefaults]
#define WCSWITCHKEY @"WCSWITCHKEY"
- (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
switchView.on = [WCDefaults boolForKey:WCSWITCHKEY];
[switchView addTarget:self action:@selector(switchChang:) forControlEvents:(UIControlEventValueChanged)];
....
}
%new
-(void)switchChang:(UISwitch *)switchView{
[WCDefaults setBool:switchView.isOn forKey:WCSWITCHKEY];
[WCDefaults synchronize];
}
%new
- (void)textFieldDidChangeValue:(NSNotification *)notification{
UITextField *sender = (UITextField *)[notification object];
[WCDefaults setValue:sender.text forKey:WCTIMEKEY];
[WCDefaults synchronize];
}
二 hook到收到红包消息的方法
- 分析聊天界面构成
- 手机停留在和某人聊天的界面,然后cycript连接手机调试
获取全部信息:cy# UIApp.keyWindow.recursiveDescription.toString()
- 然后找到某个label,比如显示时间的label然后一层一层用cycript [0x11ad4af30 nextResponder]查看,一层一层的找到BaseMsgContentViewController就是聊天界面控制器
- 或者直接用Xcode的debug view 找到BaseMsgContentViewController
- 找到BaseMsgContentViewController后就需要找到哪个方法是红包消息方法,可以hook所有方法,然后打印输出。手动方法(不建议)
- 利用theos的logify.pl把BaseMsgContentViewController变成BaseMsgContentViewControllerHook.xm
logify.pl /Users/mac/Desktop/wechatHeaders/BaseMsgContentViewController.h > ./BaseMsgContentViewControllerHook.xm
- 把BaseMsgContentViewControllerHook.xm拖到工程文件夹的logos文件夹下,拖的时候target要勾选工程文件的target。然后build一下
- build过后生成BaseMsgContentViewControllerHook.mm,把它拖到logos文件夹下,target要勾选动态库
- 要想编译通过,那么要把微信相关的头文件都拖到工程中
- 上面hook所有方法的方式太慢,MonkeyApp可以直接完成(速度很快)
-
找到工程的Config文件下的MethodTraceConfig.plist,把ENABLE_METHODTRACE改为YES,然后把BaseMsgContentViewController添加上去:
- 然后运行,发消息测试一下,Xcode输出。在输出中可以看到与Message相关的只有addMessageNode:和getMessageChatContactByMessageWrap:两个方法
-
在BaseMsgContentViewControllerHook.h中找到addMessageNode:和getMessageChatContactByMessageWrap:然后只hook这两个方法:
- hook消息方法
//红包逻辑分析
%group group4
%hook BaseMsgContentViewController
- (void)addMessageNode:(id)arg1 layout:(_Bool)arg2 addMoreMsg:(_Bool)arg3
{
%orig;
}
%end
%end
断点到addMessageNode,然后po arg1可以看出addMessageNode方法就是消息收发相关的方法,那么当新消息来的时候,查看函数调用栈,就知道是哪个对象在管理消息的收发。可以通过恢复符号表找到这个对象
- 利用restore-symbol 恢复符号表
- 进入到restore-symbol,然后
make restore-symbol
- 把TargetApp里的WeChat.app里的macho找到,然后拆分
lipo WeChat -thin arm64 -output WeChat_arm64
- 恢复符号表:
./restore-symbol WeChat_arm64 -o weichat_symbol
- 把weichat_symbol的名字改为WeChat,替换WeChat.app包内容里的WeChat
- 恢复符号表后运行,收到新消息的函数调用栈:bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
frame #0: 0x00000001078189dc lib003--WeiChatTestDylib.dylib`_logos_method$group4$BaseMsgContentViewController$addMessageNode$layout$addMoreMsg$(self=0x00000001089b1e00, _cmd="addMessageNode:layout:addMoreMsg:", arg1=0x00000001166881a0, arg2=true, arg3=false) at _03__WeiChatTestDylib.xm:217
* frame #1: 0x0000000102d7f3ec WeChat`-[BaseMsgContentLogicController DidAddMsg:] + 520
frame #2: 0x0000000102d67250 WeChat`-[BaseMsgContentLogicController OnAddMsg:MsgWrap:] + 360
frame #3: 0x0000000106603980 MMCommon`_callExtension + 480
frame #4: 0x0000000103647dd4 WeChat`-[CMessageMgr MainThreadNotifyToExt:] + 560
frame #5: 0x00000001850ca0ec Foundation`__NSThreadPerformPerform + 340
可以看出frame #4: 0x0000000103647dd4 WeChat`-[CMessageMgr MainThreadNotifyToExt:]
就是消息中转站
hook消息相关方法:
%hook BaseMsgContentLogicController
- (void)DidAddMsg:(id)arg1{
%orig;
}
- (void)OnAddMsg:(id)arg1 MsgWrap:(id)arg2{
%orig;
}
%end
%hook CMessageMgr
- (void)MainThreadNotifyToExt:(id)arg1{
%orig;
}
%end
可以分析出CMessageMgr这个类就是消息中转站
- 现在又hook住CMessageMgr的所有方法,分析出onNewSyncAddMessage:方法就是接收新消息的方法
- (void)onNewSyncAddMessage:(id)arg1{
NSLog(@"%@\n%@",arg1,[arg1 class]);
%orig;
}
输出:
{m_uiMesLocalID=9, m_ui64MesSvrID=6148490851876887842, m_nsFromUsr=wxi*241~19, m_nsToUsr=wxi*l12~19, m_uiStatus=3, type=1, msgSource="<msgsource><sequence_id>634140695</sequence_id></msgsource>"}
CMessageWrap
分析CMessageWrap的type=49的时候是红包消息
三 hook开红包的方法
- 找到开红包的按钮
- 手机停留在开红包界面,cycript调试,输出界面所有信息
UIApp.keyWindow.recursiveDescription.toString()
- 随便改一个label的text看界面是否改变,然后查看上一层:
cy# #0x10eb580b0.text = "meryin"
"meryin"
cy# [#0x10eb580b0 nextResponder]
#"<UIImageView: 0x10c4c18c0; frame = (25 116.5; 325 434); autoresize = LM+RM+TM+BM; layer = <CALayer: 0x10d061800>>"
- UIImageView查看其所有子view
cy# # 0x10c4c18c0.subviews
所有子控件中有两个UIButton,根据其frame,找到并验证UIButton: 0x116d71840; frame = (107.5 233; 110 110)
就是拆红包按钮
- 找到拆红包按钮的target
cy# #0x116d71840.allTargets
[NSSet setWithArray:@[#"<WCRedEnvelopesReceiveHomeView: 0x11393a310; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x10d3a4e40>>"]]]
WCRedEnvelopesReceiveHomeView就是UIButton 的target
- 找到拆红包的方法
利用cyprit 一个方法一个方法的试:
cy# [#0x11393a310 OnOpenRedEnvelopes]
WCRedEnvelopesReceiveHomeView 类中的OnOpenRedEnvelopes就是开红包的方法
以上只能在拆红包界面才能自动拆红包,我们的目的是,无论在哪个界面或者后台运行,只要红包消息来了就能自动拆红包
- 静态分析OnOpenRedEnvelopes
- 用ida静态分析OnOpenRedEnvelopes
#_OBJC_IVAR_$_WCRedEnvelopesReceiveHomeView.m_dicBaseInfo@PAGE
__text:0000000101157290 LDRSW X24, [X8,#_OBJC_IVAR_$_WCRedEnvelopesReceiveHomeView.m_dicBaseInfo@PAGEOFF] ; NSDictionary *m_dicBaseInfo;
.....
#_OBJC_IVAR_$_WCRedEnvelopesReceiveHomeView.m_delegate@PAGE
__text:0000000101157378 LDRSW X8, [X8,#_OBJC_IVAR_$_WCRedEnvelopesReceiveHomeView.m_delegate@PAGEOFF] ; WCRedEnvelopesReceiveHomeViewDelegate *m_delegate;
#selRef_WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes@PAGE
[X8,#selRef_WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes@PAGEOFF]
__text:0000000101157390 BL _objc_msgSend
分析可以看出这个方法大概执行了,拿到 NSDictionary *m_dicBaseInfo
做一系列判定,然后拿到id <WCRedEnvelopesReceiveHomeViewDelegate> m_delegate;
然后m_delegate发送消息WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes
- hook OnOpenRedEnvelopes
%hook WCRedEnvelopesReceiveHomeView
- (void)OnOpenRedEnvelopes{
NSDictionary *dict = MSHookIvar<NSDictionary*>(self,"m_dicBaseInfo");
NSArray *arr = dict.allKeys;
for(int i=0;i<arr.count;i++){
NSLog(@"%@:%@",arr[i],[dict objectForKey:arr[i]]);
}
id delegate = MSHookIvar<id >(self,"m_delegate");
NSLog(@"---%@",[delegate class]);
}
%end
打印输出:
2018-05-23 17:51:00.011658+0800 WeChat[9997:949894] wishing:恭喜发财,大吉大利
2018-05-23 17:51:00.011940+0800 WeChat[9997:949894] watermark:
2018-05-23 17:51:00.012051+0800 WeChat[9997:949894] sendUserName:wxid_hy6hye79l4q241
2018-05-23 17:51:00.012153+0800 WeChat[9997:949894] timingIdentifier:7149DB99A65AA9FDEAC9095342D16F8D
2018-05-23 17:51:00.012487+0800 WeChat[9997:949894] hbStatus:2
2018-05-23 17:51:00.012603+0800 WeChat[9997:949894] receiveStatus:0
2018-05-23 17:51:00.012757+0800 WeChat[9997:949894] sendId:1000039401201805237018373647074
2018-05-23 17:51:00.012921+0800 WeChat[9997:949894] isSender:0
2018-05-23 17:51:00.013326+0800 WeChat[9997:949894] statusMess:给你发了一个红包
2018-05-23 17:51:00.013543+0800 WeChat[9997:949894] hbType:0
2018-05-23 17:51:00.013752+0800 WeChat[9997:949894] ---WCRedEnvelopesReceiveControlLogic
可以看出拆红包的核心代码就是WCRedEnvelopesReceiveControlLogic类中的 WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes方法
- 静态分析 WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes
ida找到ctrl+f键找到WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes方法,然后按f5就可以还原伪代码
调用类方法 [%c(类名称) 方法名]
%hook WCRedEnvelopesReceiveControlLogic
- (void)WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes{
WCRedEnvelopesControlData *m_data = MSHookIvar<WCRedEnvelopesControlData* >(self,"m_data");
CMessageWrap *m_Wrap =[m_data m_oSelectedMessageWrap];
WCPayInfoItem *item =[m_Wrap m_oWCPayInfoItem];
NSString *url =[item m_c2cNativeUrl];
NSInteger length = [@"wxpay://c2cbizmessagehandler/hongbao/receivehongbao?" length];
id componets = [url substringFromIndex:length];
//调用类方法 [%c(类名称) 方法名]
NSDictionary *dictionary=[%c(WCBizUtil) dictionaryWithDecodedComponets:componets separator:@"&"];
NSLog(@"dic===%@",dictionary);
NSMutableDictionary *mul_dict =[%c(NSMutableDictionary) dictionary];
[mul_dict setObject:@"1" forKey:@"msgType"];
[mul_dict setObject:dictionary[@"sendid"] forKey:@"sendId"];
[mul_dict setObject:dictionary[@"channelid"] forKey:@"channelId"];
CContactMgr *server =[[%c(MMServiceCenter) defaultCenter] getService:[%c(CContactMgr) class]];
CContact *contact = [server getSelfContact];
id displayName = [contact getContactDisplayName];
[mul_dict setObject:displayName forKey:@"nickName"];
id m_nsHeadImgUrl = [contact m_nsHeadImgUrl];
[mul_dict setObject:m_nsHeadImgUrl forKey:@"headImg"];
CMessageWrap *warp =[m_data m_oSelectedMessageWrap];
if ( warp )
{
[mul_dict setObject:url forKey:@"nativeUrl"];
}
MMMsgLogicManager *logicserver =[[%c(MMServiceCenter) defaultCenter] getService:[%c(MMMsgLogicManager) class]];
WeixinContentLogicController *currentLogicController = [logicserver GetCurrentLogicController];
NSLog(@"--%@",[currentLogicController class]);
if ( currentLogicController )
{
id m_contact =[currentLogicController m_contact];
if ( m_contact )
{
NSString *m_nsUsrName =[m_contact m_nsUsrName];
if ( m_nsUsrName )
{
[mul_dict setObject:m_nsUsrName forKey:@"sessionUserName"];
}
}
}
NSDictionary *m_struct = [m_data m_structDicRedEnvelopesBaseInfo];
NSString *timingIdentifier= [m_struct objectForKey:@"timingIdentifier"]
if ( [timingIdentifier length])
{ [mul_dict setObject:timingIdentifier forKey:@"timingIdentifier"];}
WCPayLogicMgr *payLogicMgr =[[%c(MMServiceCenter) defaultCenter] getService:[%c(WCPayLogicMgr) class]];
[payLogicMgr setRealnameReportScene:1003];
id subScript = [m_struct objectForKeyedSubscript:@"agree_duty"];
/*
v97 = _NSConcreteStackBlock;
v98 = 3254779904LL;
v99 = sub_1015C17E0;
v100 = &unk_10390BBD0;
v101 = v17;
v86 = objc_retain(v17);
v87 = objc_retain(v72);
v102 = v87;
v91 = _NSConcreteStackBlock;
v92 = 3254779904LL;
v93 = sub_1015C18F8;
v94 = &unk_10390BC00;
objc_copyWeak(&v96, &v103);
v95 = objc_retain(v87);
objc_msgSend(v80, "checkHongbaoOpenLicense:acceptCallback:denyCallback:", v85, &v97, &v91);
*/
}
%end
以上除了block其他都还原了
- 还原block符号表
- ida->file->script file 选中ida_search_block.py 生成block_symbol.json文件
- block_symbol.json 和restore-symbol放在一起 然后
./restore-symbol WeChat -o wwchat_block -j block_symbol.json
- 再把wwchat_block改名为WeChat替换微信包内容里的WeChat
- 再把WeChat用ida打开,然后还原代码
[payLogicMgr checkHongbaoOpenLicense:subScript acceptCallback:^(){
WCRedEnvelopesLogicMgr *envelopesLogicMgr = [[%c(MMServiceCenter) defaultCenter] getService:[%c(WCRedEnvelopesLogicMgr) class]];
[envelopesLogicMgr OpenRedEnvelopesRequest:mul_dict];
}denyCallback:^(){
}];
可以分析出WCRedEnvelopesLogicMgr就是红包管理类,OpenRedEnvelopesRequest:就是开红包的关键方法
接下篇