本篇将讲述如何将WX的设置界面添加两个自定义的UI行: 包含是否启用某功能的开关,以及手速设置.并且如何定位到修改的代码.采用的是砸过壳的包.
成品也就是增加了两个UI及开关联动效果、
界面分析
- 如果我们要破解别人的App, 首先从界面UI入手,定位UI
- 1、使用class-dump导出全部头文件
class-dump -H WeChat -o ./WeChatHeaders
-
- 2、使用MonkeyDev重签名wx8.0.2
- 3、真机运行项目,使用Debug View找到设置页的控制器名称 NewSettingViewController
- 4、使用Cycript附加当前进程
sh cyConnect.sh
-
- 5、使用 pvcs() 找到设置页的控制器
# pvcs()
-
- 6、打印控制器View下的所有视图,从中找到关键类 WCTableViewManager
-
- 7、打开WCTableViewManager.h文件,找到数据源和关键方法
-
-
- tableView数据源关键方法
-
-
- 8、精准定位注入点
- 找到影响UITableView展示行数的数据源
- 对WCTableViewManager中的 numberOfSectionsInTableView方法进行HOOK,打印数组总数和Section数
- 想要精准定位,需要在WCTableViewManager中,对所属控制器进行判断
- 找到WCTableViewManager和控制器的关联,找到WCTableViewManager
- 找到UITableView,通过响应链条,向下找一层 0xAddress.tableView.nextResponder
- 通过响应链条,再向下找一层: 0xAddress.tableView.nextResponder.nextResponder,
- 最终找到 NewSettingViewController
- 9、修改界面
- 确保代码仅在NewSettingViewController中生效,接下来对几个关键方法进行HOOK,将界面修改成我们预期的样子
- 增加Section
- 8、精准定位注入点
// 增加Section
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if([tableView.nextResponder.nextResponder isKindOfClass: %c(NewSettingViewController)]) {
return %orig+1;
}
return %orig;
}
-
-
- 增加Section下的row
-
//Section下面的Rows,增加2行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)] && (section==[self numberOfSectionsInTableView:tableView]-1)){
return 2;
}
}
-
-
- 自定义cell高度
-
//自定义Cell的高度,固定为60
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)] && (indexPath.section==[self numberOfSectionsInTableView:tableView]-1)){
return 60;
}
return %orig;
}
-
-
- 自定义Cell背景、查看运行结果
-
//自定义Cell,只设置背景色,看一下运行后的结果,确认HOOK代码的有效性
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)] && (indexPath.section==[self numberOfSectionsInTableView:tableView]-1)){
NSString *strIdentifier=[NSString stringWithFormat:@"HookCell_%i",(int)indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:strIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:strIdentifier];
}
if(indexPath.row==0){
cell.backgroundColor=[UIColor redColor];
}
else{
cell.backgroundColor=[UIColor blueColor];
}
return cell;
}
return %orig;
}
-
- 10、完善界面
- 确保HOOK代码是有效的,完善自定义Cell的界面
- 将自定义图标,导入WeChat包,
- 完善tableView:cellForRowAtIndexPath方法
- 10、完善界面
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)] && (indexPath.section==[self numberOfSectionsInTableView:tableView]-1)){
NSString *strIdentifier=[NSString stringWithFormat:@"HookCell_%i",(int)indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:strIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:strIdentifier];
}
cell.backgroundColor = [UIColor whiteColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if(indexPath.row==0){
BOOL isAutoEnable = NO;
cell.imageView.image = [UIImage imageNamed:(isAutoEnable ? @"unlocked" : @"locked")];
cell.textLabel.text = @"自动抢红包";
UISwitch *switchAuto = [[UISwitch alloc] init];
[switchAuto addTarget:self action:@selector(hookAutoAction:) forControlEvents:UIControlEventValueChanged];
switchAuto.on=isAutoEnable;
cell.accessoryView = switchAuto;
}
else{
cell.imageView.image = [UIImage imageNamed:@"clock"];
cell.textLabel.text = @"等待时间(秒)";
UITextField *txtWait=[[UITextField alloc] initWithFrame:CGRectMake(0, 0, 150, 40)];
txtWait.borderStyle = UITextBorderStyleRoundedRect;
txtWait.backgroundColor = [UIColor whiteColor];
txtWait.keyboardType = UIKeyboardTypeNumberPad;
txtWait.returnKeyType = UIReturnKeyDone;
cell.accessoryView = txtWait;
}
return cell;
}
return %orig;
}
-
-
- 增加UISwitch切换时,触发的hookAutoAction:方法
-
%new
-(void)hookAutoAction:(UISwitch *)sender{
NSLog(@"自动抢红包:%@", (sender.isOn ? @"启用" : @"禁用"));
}
%end
-
-
- 真机运行项目,查看UI效果
- 11、 实现功能
- UI搭建完成后,实现交互功能的启用/禁用标识,以及手速设置,都要进行本地化保存
- 增加宏定义
-
#define HOOKAUTOVALUE @"HookAutoValue"
#define HOOKWAITVALUE @"HookWaitValue"
-
-
- 实现UISwitch切换的逻辑
-
%new
-(void)hookAutoAction:(UISwitch *)sender{
[[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:HOOKAUTOVALUE];
[[NSUserDefaults standardUserDefaults] synchronize];
[MSHookIvar<UITableView *>(self,"_tableView") reloadData];
}
-
-
- 修改tableView:cellForRowAtIndexPath:方法,将UI和功能进行关联
-
BOOL isAutoEnable = [[NSUserDefaults standardUserDefaults] boolForKey:HOOKAUTOVALUE];
cell.imageView.image = [UIImage imageNamed:(isAutoEnable ? @"unlocked" : @"locked")];
cell.textLabel.text = @"自动抢红包";
UISwitch *switchAuto = [[UISwitch alloc] init];
[switchAuto addTarget:self action:@selector(hookAutoAction:) >forControlEvents:UIControlEventValueChanged];
switchAuto.on=isAutoEnable;
cell.accessoryView = switchAuto;
-
- 完成手速设置逻辑
- 添加UITextFieldDelegate
- 完成手速设置逻辑
@interface WCTableViewManager : NSObject <UITextFieldDelegate>
@property(retain, nonatomic) NSMutableArray *sections;
@end
-
- 增加textField:shouldChangeCharactersInRange:replacementString:方法,文本框内输入\n,视为输入完成,自动收起键盘
%new
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if ([string isEqualToString:@"\n"]) {
[textField resignFirstResponder];
return NO;
}
return YES;
}
-
- 增加textFieldDidEndEditing:方法,输入完成,将文本框内存本地化保存
%new
-(void)textFieldDidEndEditing:(UITextField *)textField {
[[NSUserDefaults standardUserDefaults] setObject:textField.text forKey:HOOKWAITVALUE];
[[NSUserDefaults standardUserDefaults] synchronize];
}
-
- 修改tableView:cellForRowAtIndexPath:方法,将UI和功能进行关联
cell.imageView.image = [UIImage imageNamed:@"clock"];
cell.textLabel.text = @"等待时间(秒)";
UITextField *txtWait=[[UITextField alloc] initWithFrame:CGRectMake(0, 0, 150, 40)];
txtWait.borderStyle = UITextBorderStyleRoundedRect;
txtWait.backgroundColor = [UIColor whiteColor];
txtWait.keyboardType = UIKeyboardTypeNumberPad;
txtWait.returnKeyType = UIReturnKeyDone;
txtWait.delegate = self;
txtWait.text = [[NSUserDefaults standardUserDefaults] objectForKey:HOOKWAITVALUE];
cell.accessoryView = txtWait;
-
- 12、优化:
- 整体的界面和功能都已经完成,还有两个小问题需要优化
- 12、优化:
-
-
-
- 触发文本框,键盘弹出,会遮挡底部的功能区域
- 设置页的列表滑动时,键盘无法自动收起,影响体验
- 解决遮挡问题
- 对NewSettingViewController进行HOOK,对键盘的通知进行监听和销毁
-
-
%hook NewSettingViewController
- (void)viewDidLoad{
%orig;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
%end
-
-
- 实现键盘弹出方法
-
%new
- (void)keyboardWillShow:(NSNotification *)notification {
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGSize viewSize = self.view.frame.size;
self.view.frame = CGRectMake(0, -keyboardSize.height, viewSize.width, viewSize.height);
}
-
-
- 实现键盘收起方法
-
%new
- (void)keyboardWillHide:(NSNotification *)notification {
CGSize viewSize = self.view.frame.size;
self.view.frame = CGRectMake(0, 0, viewSize.width, viewSize.height);
}
-
-
- 解决列表滑动,自动收起键盘问题
- 对NewSettingViewController进行HOOK,修改viewDidLoad方法
- 增加UITableView.keyboardDismissMode属性的设置
- 解决列表滑动,自动收起键盘问题
-
- (void)viewDidLoad{
%orig;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
WCTableViewManager *m_tableViewMgr = MSHookIvar<WCTableViewManager *>(self, "m_tableViewMgr");
[MSHookIvar<UITableView *>(m_tableViewMgr, "_tableView") setKeyboardDismissMode:UIScrollViewKeyboardDismissModeOnDrag];
}
总结:
-
- UI搭建
- 使用class-dump,导出目标App的头文件
- 使用MonkeyDev重签名并运行App
- 使用Debug View,快速定位目标控制器
- 使用Cycript、分析控制器中的视图、对象、数据源
- 在对应的头文件中,找到关键的方法和属性
- 需要精准定位到注入点,不能影响其他功能
- 可以使用响应链条,找到控件与所属控制器的关联
- 需要自定义图标,直接将图片导入App包即可
- 添加的方法,方法名称加上自定义前缀,保证命名唯一
- HOOK关键方法,完成界面与功能
- NSHookIvar: 获取对象下的成员变量
- UI搭建