###工具
- cycript
- iResign
- Xcode
###分析前
- ssh 连接到越狱的手机
- cycript -p WeChat
###找到聊天界面所在的viewController 以及内存地址
- 打印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];
}
}
到此,一键录音的功能已经修改完成!但是,实际上还需要一个“结束录音”的按钮!弄了一下下,发现有几个坑没跳过去…就等后面再分析分析。
##结束语
没什么技术含量,第一次分享!!多多包涵!