iOS逆向篇之微信抢红包(上)

本文仅供学习和参考,建议不用于商业用途,若用于商业用途后果自负
完整的工程地址

一 设置界面添加抢红包功能

在设置界面添加抢红包开关和延迟时间的两个cell

  1. 建立MonkeyApp工程,把微信ipa拖到TargetApp文件夹中,真机运行
  2. 用Xcode的Debug view找到设置界面的代码注入的地方


    11.png
  • 设置界面的控制器是: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代理中设置

  1. 代码注入
%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
  1. 本地数据缓存
  • 图片直接拖到工程的Dylib文件夹中


    22.png
  • 数据直接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到收到红包消息的方法

  1. 分析聊天界面构成
  • 手机停留在和某人聊天的界面,然后cycript连接手机调试
    获取全部信息:cy# UIApp.keyWindow.recursiveDescription.toString()
  • 然后找到某个label,比如显示时间的label然后一层一层用cycript [0x11ad4af30 nextResponder]查看,一层一层的找到BaseMsgContentViewController就是聊天界面控制器
  • 或者直接用Xcode的debug view 找到BaseMsgContentViewController
  1. 找到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要勾选动态库
  • 要想编译通过,那么要把微信相关的头文件都拖到工程中
  1. 上面hook所有方法的方式太慢,MonkeyApp可以直接完成(速度很快)
  • 找到工程的Config文件下的MethodTraceConfig.plist,把ENABLE_METHODTRACE改为YES,然后把BaseMsgContentViewController添加上去:


    11.png
  • 然后运行,发消息测试一下,Xcode输出。在输出中可以看到与Message相关的只有addMessageNode:和getMessageChatContactByMessageWrap:两个方法
  • 在BaseMsgContentViewControllerHook.h中找到addMessageNode:和getMessageChatContactByMessageWrap:然后只hook这两个方法:


  1. hook消息方法
//红包逻辑分析
%group group4
%hook BaseMsgContentViewController

- (void)addMessageNode:(id)arg1 layout:(_Bool)arg2 addMoreMsg:(_Bool)arg3
{
    %orig;
}
%end

%end

断点到addMessageNode,然后po arg1可以看出addMessageNode方法就是消息收发相关的方法,那么当新消息来的时候,查看函数调用栈,就知道是哪个对象在管理消息的收发。可以通过恢复符号表找到这个对象

  1. 利用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
  1. 恢复符号表后运行,收到新消息的函数调用栈: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开红包的方法

  1. 找到开红包的按钮
  • 手机停留在开红包界面,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)就是拆红包按钮

  1. 找到拆红包按钮的target
cy# #0x116d71840.allTargets
[NSSet setWithArray:@[#"<WCRedEnvelopesReceiveHomeView: 0x11393a310; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x10d3a4e40>>"]]]

WCRedEnvelopesReceiveHomeView就是UIButton 的target

  1. 找到拆红包的方法
    利用cyprit 一个方法一个方法的试:
cy# [#0x11393a310 OnOpenRedEnvelopes]

WCRedEnvelopesReceiveHomeView 类中的OnOpenRedEnvelopes就是开红包的方法
以上只能在拆红包界面才能自动拆红包,我们的目的是,无论在哪个界面或者后台运行,只要红包消息来了就能自动拆红包

  1. 静态分析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方法

  1. 静态分析 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其他都还原了

  1. 还原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:就是开红包的关键方法

接下篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容