微信“按住 说话”改成“一键 录音”


#1

###工具

  • cycript
  • iResign
  • Xcode

###分析前

  1. ssh 连接到越狱的手机
  2. cycript -p WeChat

###找到聊天界面所在的viewController 以及内存地址

  1. 打印App的rootViewController
//通过keyWindow获取rootViewController,0x15ceb31b0是rootcontroller对象的内存地址
cy# rootcontroller = [[UIApplication sharedApplication ] keyWindow ].rootViewController 
#"<MMTabBarController: 0x15ceb31b0>"

2.显示MMTabBarController的子controller

cy# rootcontroller.viewControllers
@[
#"<MMUINavigationController: 0x15ceaa330>",
#"<MMUINavigationController: 0x15ceaee40>",
#"<MMUINavigationController: 0x15cd3c9d0>",
#"<MMUINavigationController: 0x15ceb12b0>"
]

3.找到聊天界面所在viewController

//可以通过‘#’号来获取指定内存地址的对象,并赋值给变量navcontroller
cy# navcontroller=#0x15ceaa330
#"<MMUINavigationController: 0x15ceaa330>"
//打印出子controller
cy# navcontroller.viewControllers
@[#"<NewMainFrameViewController: 0x15d923000>",#"<BaseMsgContentViewController: 0x15da32200>"]
//取指定内存地址对象,并赋值给变量msgviewcontroller
cy# msgviewcontroller=#0x15da32200
#"<BaseMsgContentViewController: 0x15da32200>"

###找到“按住 说话”按钮所在的view和内存地址
1.显示聊天界面的views

cy# #0x15da32200.view.subviews() 
@[
//可以发现该view包含了3个子view
#"<MMMultiSelectToolView: 0x15cea00e0; frame = (0 568; 320 50); hidden = YES; layer = <CALayer: 0x170837a00>>",
#"<MMTableView: 0x15db28c00; baseClass = UITableView; frame = (0 0; 320 568); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x1704580f0>; layer = <CALayer: 0x170838be0>; contentOffset: {0, 80}; contentSize: {320, 598}>",
#"<MMInputToolView: 0x15cf63070; frame = (0 0; 320 568); text = ''; layer = <CALayer: 0x17443d2a0>>"
]

2.打印MMInputToolView的subviews

///由第一步可以猜测出我们要找的按钮在MMInputToolView上,0x15cf63070即为MMInputToolView对象的内存地址
cy# _inputtoolview=#0x15cf63070
#"<MMInputToolView: 0x15cf63070; frame = (0 0; 320 568); text = ''; layer = <CALayer: 0x17443d2a0>>"
cy# _inputtoolview.subviews() 
@[
#"<UIButton: 0x15cf6b430; frame = (215 408; 105 110); hidden = YES; opaque = NO; layer = <CALayer: 0x170835760>>",
//底部的语音按钮所在的view,0x15cd93e30地址在下一步用到
#"<InputToolViewBar: 0x15cd93e30; baseClass = UIImageView; frame = (0 518; 320 50); clipsToBounds = YES; layer = <CALayer: 0x170227140>>",
#"<MMUIView: 0x15cd964a0; frame = (0 568; 320 224); layer = <CALayer: 0x1746217c0>>",
#"<RecordView: 0x15cd97190; frame = (0 0; 320 300); hidden = YES; layer = <CALayer: 0x170835d20>>",
#"<SelectAttachmentView: 0x15cd4b610; frame = (0 568; 320 224); autoresize = W; layer = <CALayer: 0x17482b460>>",
#"<ShortVideoToolbar: 0x15ce90f00; frame = (0 588; 320 348.154); layer = <CALayer: 0x171034a60>>",
#"<UIButton: 0x15cf67fd0; frame = (240 408; 81 110); alpha = 0; opaque = NO; layer = <CALayer: 0x1708330a0>>"
]

3.对上面输出的view逐个进行hidden = YES 调试以确定“按住说话”按钮

cy# #0x15cd93e30.hidden=YES
true

4.打印InputToolViewBar

cy# #0x15cd93e30.subviews() 
@[
#"<UIVisualEffectView: 0x15e16f210; frame = (0 0; 320 50); autoresize = W+H; tag = 102289; layer = <CALayer: 0x174a29640>>",
#"<MMGrowTextView: 0x15cd88d20; frame = (37 3; 209 44); text = ''; layer = <CALayer: 0x174a243c0>>",
#"<UIView: 0x15cd942a0; frame = (0 0; 320 0.5); autoresize = W; layer = <CALayer: 0x17443c920>>",

//同样通过逐个view设置hidden,可以定位到这里就是"按住说话"按钮,地址0x15cf669c0在下面会用到
#"<MMTransparentButton: 0x15cf669c0; baseClass = UIButton; frame = (37 2.5; 209 45); alpha = 0; opaque = NO; autoresize = W; layer = <CALayer: 0x170832240>> hightlighted = 0",
#"<UIButton: 0x15cd943b0; frame = (1 8; 35 35); opaque = NO; tag = 10088; layer = <CALayer: 0x17443e100>>",
#"<UIButton: 0x15cd95e80; frame = (284 8; 35 35); opaque = NO; autoresize = LM; layer = <CALayer: 0x17443db20>>",
#"<UIButton: 0x15cd95a60; frame = (247 8; 35 35); opaque = NO; autoresize = LM; layer = <CALayer: 0x17443e480>>",
//注意,这里的按钮并不是"按住说话"按钮
#"<MMTransparentButton: 0x15cf69430; baseClass = UIButton; frame = (0 2.5; 320 40); alpha = 0; opaque = NO; layer = <CALayer: 0x1708355e0>> hightlighted = 0"
]

5.确定“按住说话”按钮所属的对象以及内存地址

cy# #0x15df32830.subviews() 
@[
#"<UIImageView: 0x15df3b1f0; frame = (0 0; 209 45); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x170830a80>>",
//text对应的值应该是一个ascii编码,对应中文就是:按住 说话
#"<UIButtonLabel: 0x15df33520; frame = (70.5 14.5; 68.5 19.5); text = '\xe6\x8c\x89\xe4\xbd\x8f \xe8\xaf\xb4\xe8\xaf\x9d'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x17068f3c0>>"
]

到这里,可以确定button所在的类:MMInputToolView ,以及button的内存地址:0x15cf69430。下一步通过WeChat 头文件中的函数来逐个测试,看看那个函数调起了语音,哪个函数停止语音,哪个函数发送语音等额那个。下面会用到button的内存地址进行调试。

通过使用下面两两句代码来获取button的action,发现为空的。于是就想到,按钮应该是通过触摸事件来实现点击、取消等效果的,那么直接看看头文件先吧!(这段是后面补上的)

 [button allTargets];
 [button actionsForTarget:<#(nullable id)#> forControlEvent:<#(UIControlEvents)#>]

###查找头文件,找到开始录音、停止录音函数
1.在MMInputToolView.h文件的219行和249行

- (void)resalStartRecording;//开始录音
- (void)stopRecording:(id)arg1;//停止录音,并发送

2.进行方法调用,并观察微信聊天界面,可以发现已经开始录音!!

//开始录音
cy# [_inputtoolview resalStartRecording ]
cy# 
//结束录音,这里的内存地址0x15df32830 就是上面所说的“按住 说话”按钮的的对象内存地址
cy# [_inputtoolview stopRecording:#0x15df32830]
cy# 

##开始hook
这里直接上代码

//一键录音
CHMethod(2, void,MMInputToolView,MMTransparentButton_touchesEnded,id,arg1,withEvent,id,arg2)
{
//SpreadButtonManager 是我自己写的一个单例,大家可以忽略
    if ([SpreadButtonManager sharedInstance].oneKeyRecord) {
        return;
    }
    CHSuper(2, MMInputToolView,MMTransparentButton_touchesEnded,arg1,withEvent,arg2);
}
//一键录音
CHMethod(2, void,MMInputToolView,MMTransparentButton_touchesMoved,id,arg1,withEvent,id,arg2)
{
    if (![SpreadButtonManager sharedInstance].oneKeyRecord) {
        return;
    }
    CHSuper(2, MMInputToolView,MMTransparentButton_touchesMoved,arg1,withEvent,arg2);
}
//一键录音
CHMethod(2, void,MMInputToolView,MMTransparentButton_touchesCancelled,id,arg1,withEvent,id,arg2)
{
    if (![SpreadButtonManager sharedInstance].oneKeyRecord) {
        return;
    }
    CHSuper(2, MMInputToolView,MMTransparentButton_touchesCancelled,arg1,withEvent,arg2);
}
//一键录音
CHMethod(2, void,MMInputToolView,MMTransparentButton_touchesBegan,id,arg1,withEvent,id,arg2)
{
    if (![SpreadButtonManager sharedInstance].oneKeyRecord) {
        CHSuper(2, MMInputToolView,MMTransparentButton_touchesBegan,arg1,withEvent,arg2);
        return;
    }
    UIViewController *selfVC = [UIApplication itx_topViewController];
    Ivar inputToolViewIvar = class_getInstanceVariable(objc_getClass("BaseMsgContentViewController"), "_inputToolView");
    UIView *inputtoolView = object_getIvar(selfVC, inputToolViewIvar);
    NSLog(@"调用toolView resalStartRecording方法:%@",inputtoolView);
    [inputtoolView performSelector:@selector(resalStartRecording) withObject:nil];

}

下面这段代码,仅仅只是用来修改按钮名字

//每次显示都修改按钮名字
CHMethod(1,void,BaseMsgContentViewController,viewWillAppear,BOOL, animated)
{
    CHSuper(1, BaseMsgContentViewController,viewWillAppear,animated);
    UIViewController *selfVC = [UIApplication itx_topViewController];
    if (![selfVC isKindOfClass:objc_getClass("BaseMsgContentViewController")]) {
        return;
    }
    Ivar inputToolViewIvar = class_getInstanceVariable(objc_getClass("BaseMsgContentViewController"), "_inputToolView");
    id inputToolView = object_getIvar(selfVC, inputToolViewIvar);
    NSLog(@"逆向日志>> selfVC:%@, 变量toolView:%@",selfVC,inputToolView);
    //获取录音按钮对象
    Ivar recordButtonIvar = class_getInstanceVariable(objc_getClass("MMInputToolView"),"_recordButton");
    UIButton *recordButton = object_getIvar(inputToolView, recordButtonIvar);
    
    if ([SpreadButtonManager sharedInstance].oneKeyRecord) {
        [(UIButton *)recordButton setTitle:@"一键 说话" forState:0];
    }else{
        [(UIButton *)recordButton setTitle:@"按住 说话" forState:0];
    }
}

到此,一键录音的功能已经修改完成!但是,实际上还需要一个“结束录音”的按钮!弄了一下下,发现有几个坑没跳过去…就等后面再分析分析。

##结束语
没什么技术含量,第一次分享!!多多包涵!:grin:


#2

能发一下tweak吗 哈哈 hook哪里看不懂


#3

想要一个这样的微信✪ω✪