使用MokeyDev對(duì)WeChat進(jìn)行重簽名并安裝,在設(shè)置頁(yè),增加自動(dòng)搶紅包的UI,包含是否啟用自動(dòng)搶紅包功能的開(kāi)關(guān),以及搶紅包時(shí)的手速設(shè)置
界面分析
使用
class-dump導(dǎo)出全部頭文件./class-dump -H WeChat -o ./header/
使用
MokeyDev重簽名wx8.0.2.ipa真機(jī)運(yùn)行項(xiàng)目,使用
Debug Viwe找到設(shè)置頁(yè)的控制器名稱(chēng)
使用
Debug Viwe時(shí),如果經(jīng)常卡死,可以先將其暫停/繼續(xù)一次
使用
Cycript附加進(jìn)程使用
pvcs()找到設(shè)置頁(yè)的控制器
打印控制器
View下的所有視圖,從中找到UITableView,并找到對(duì)應(yīng)的數(shù)據(jù)源
打開(kāi)
WCTableViewManager.h文件,找到數(shù)據(jù)源和關(guān)鍵方法
后續(xù)對(duì)關(guān)鍵方法進(jìn)行
HOOK
精準(zhǔn)定位注入點(diǎn)
找到影響
UITableView展示行數(shù)的數(shù)據(jù)源對(duì)
WCTableViewManager中的numberOfSectionsInTableView方法進(jìn)行HOOK,打印數(shù)組總數(shù)和Section數(shù)#import <UIKit/UIKit.h> @interface WCTableViewManager : NSObject @property(retain, nonatomic) NSMutableArray *sections; @end %hook WCTableViewManager - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { NSLog(@"數(shù)據(jù)源:%ld,Sections:%ld", (long)self.sections.count, (long)[tableView numberOfSections]); return %orig; } %end真機(jī)運(yùn)行項(xiàng)目,查看
HOOK之后的打印結(jié)果
從打印結(jié)果來(lái)看,
UITableView的顯示行數(shù)和數(shù)組總數(shù)是一致的。但也打印出其他頁(yè)面的內(nèi)容,證明WCTableViewManager在項(xiàng)目中是通用的。如果想對(duì)其HOOK,需要精準(zhǔn)定位在設(shè)置頁(yè),不能影響其他功能
想要精準(zhǔn)定位,需要在
WCTableViewManager中,對(duì)所屬控制器進(jìn)行判斷我們要找到
WCTableViewManager和控制器的關(guān)聯(lián)找到
WCTableViewManager#0x280bfe5e0 ------------------------- #"<WCTableViewManager: 0x280bfe5e0>"找到
UITableView,通過(guò)響應(yīng)鏈條,向下找一層#0x280bfe5e0.tableView.nextResponder ------------------------- #"<UIView: 0x1157e38f0; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x281edca00>>"通過(guò)響應(yīng)鏈條,再向下找一層
#0x280bfe5e0.tableView.nextResponder.nextResponder ------------------------- #"<NewSettingViewController: 0x116cb8400>"通過(guò)響應(yīng)鏈條,成功找到
NewSettingViewController
修改代碼,增加判斷條件,保證
HOOK代碼僅在設(shè)置頁(yè)有效- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)]){ NSLog(@"數(shù)據(jù)源:%ld,Sections:%ld", (long)self.sections.count, (long)[tableView numberOfSections]); } return %orig; }真機(jī)運(yùn)行項(xiàng)目,查看
HOOK之后的打印結(jié)果
僅在
NewSettingViewController中打印結(jié)果
修改界面
確保代碼僅在
NewSettingViewController中生效,接下來(lái)對(duì)幾個(gè)關(guān)鍵方法進(jìn)行HOOK,將界面修改成我們預(yù)期的樣子增加
Section- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)]){ return %orig+1; } return %orig; }
最后
Section下面的Rows,固定為2- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)] && (section==[self numberOfSectionsInTableView:tableView]-1)){ return 2; } return %orig; }
為了編譯通過(guò),需要在
WCTableViewManager中,聲明numberOfSectionsInTableView:方法@interface WCTableViewManager : NSObject <UITextFieldDelegate> - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; @end
自定義
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,只設(shè)置背景色,看一下運(yùn)行后的結(jié)果,確認(rèn)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; }
真機(jī)運(yùn)行項(xiàng)目,查看
UI效果
修改
NewSettingViewController成功,在原有界面的下方,增加了自定義Cell
完善界面
確認(rèn)
HOOK代碼是有效的,下面完善自定義Cell的界面將自定義圖標(biāo),導(dǎo)入
完善
tableView:cellForRowAtIndexPath:方法- (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 ? @"hook_auto_en" : @"hook_auto_dis")]; cell.textLabel.text = @"自動(dòng)搶紅包"; 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:@"hook_wait"]; cell.textLabel.text = @"等待時(shí)間(秒)"; 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切換時(shí),觸發(fā)的hookAutoAction:方法%new -(void)hookAutoAction:(UISwitch *)sender{ NSLog(@"自動(dòng)搶紅包:%@", (sender.isOn ? @"啟用" : @"禁用")); }
真機(jī)運(yùn)行項(xiàng)目,查看
UI效果
實(shí)現(xiàn)功能
UI搭建完成后,還差最后一步,實(shí)現(xiàn)功能自動(dòng)搶紅包功能的啟用/禁用標(biāo)識(shí),以及搶紅包時(shí)的手速設(shè)置,都要進(jìn)行本地化保存
增加宏定義
#define HOOKAUTOVALUE @"HookAutoValue" #define HOOKWAITVALUE @"HookWaitValue"
實(shí)現(xiàn)
UISwitch切換的邏輯%new -(void)hookAutoAction:(UISwitch *)sender{ [[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:HOOKAUTOVALUE]; [[NSUserDefaults standardUserDefaults] synchronize]; [MSHookIvar<UITableView *>(self,"_tableView") reloadData]; }修改
tableView:cellForRowAtIndexPath:方法,將UI和功能進(jìn)行關(guān)聯(lián)BOOL isAutoEnable = [[NSUserDefaults standardUserDefaults] boolForKey:HOOKAUTOVALUE]; cell.imageView.image = [UIImage imageNamed:(isAutoEnable ? @"hook_auto_en" : @"hook_auto_dis")]; cell.textLabel.text = @"自動(dòng)搶紅包"; UISwitch *switchAuto = [[UISwitch alloc] init]; [switchAuto addTarget:self action:@selector(hookAutoAction:) >forControlEvents:UIControlEventValueChanged]; switchAuto.on=isAutoEnable; cell.accessoryView = switchAuto;
完成搶紅包時(shí)的手速設(shè)置邏輯
添加
UITextFieldDelegate@interface WCTableViewManager : NSObject <UITextFieldDelegate> @property(retain, nonatomic) NSMutableArray *sections; @end增加
textField:shouldChangeCharactersInRange:replacementString:方法,文本框內(nèi)輸入\n,視為輸入完成,自動(dòng)收起鍵盤(pán)%new - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { if ([string isEqualToString:@"\n"]) { [textField resignFirstResponder]; return NO; } return YES; }增加
textFieldDidEndEditing:方法,輸入完成,將文本框內(nèi)存本地化保存%new -(void)textFieldDidEndEditing:(UITextField *)textField { [[NSUserDefaults standardUserDefaults] setObject:textField.text forKey:HOOKWAITVALUE]; [[NSUserDefaults standardUserDefaults] synchronize]; }修改
tableView:cellForRowAtIndexPath:方法,將UI和功能進(jìn)行關(guān)聯(lián)cell.imageView.image = [UIImage imageNamed:@"hook_wait"]; cell.textLabel.text = @"等待時(shí)間(秒)"; 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;
真機(jī)運(yùn)行項(xiàng)目,查看
UI效果
優(yōu)化
整體的界面和功能都已經(jīng)完成,還有兩個(gè)小問(wèn)題需要優(yōu)化
- 觸發(fā)文本框,鍵盤(pán)彈出,會(huì)遮擋底部的功能區(qū)域
- 設(shè)置頁(yè)的列表滑動(dòng)時(shí),鍵盤(pán)無(wú)法自動(dòng)收起,影響體驗(yàn)
解決遮擋問(wèn)題
對(duì)
NewSettingViewController進(jìn)行HOOK,對(duì)鍵盤(pán)的通知進(jìn)行監(jiān)聽(tīng)和銷(xiāo)毀%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實(shí)現(xiàn)鍵盤(pán)彈出方法
%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); }實(shí)現(xiàn)鍵盤(pán)收起方法
%new - (void)keyboardWillHide:(NSNotification *)notification { CGSize viewSize = self.view.frame.size; self.view.frame = CGRectMake(0, 0, viewSize.width, viewSize.height); }
解決列表滑動(dòng),自動(dòng)收起鍵盤(pán)問(wèn)題
對(duì)
NewSettingViewController進(jìn)行HOOK,修改viewDidLoad方法增加
UITableView.keyboardDismissMode屬性的設(shè)置- (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]; }
真機(jī)運(yùn)行項(xiàng)目,優(yōu)化后的
UI效果
總結(jié)
自動(dòng)搶紅包
UI搭建
- 使用
class-dump,導(dǎo)出目標(biāo)App的頭文件- 使用
MokeyDev重簽名并運(yùn)行App- 使用
Debug Viwe,快速定位目標(biāo)控制器- 使用
Cycript,分析控制器中的視圖、對(duì)象、數(shù)據(jù)源- 在對(duì)應(yīng)的頭文件中,找到關(guān)鍵的方法和屬性
- 需要精準(zhǔn)定位到注入點(diǎn),不能影響其他功能
- 可以使用響應(yīng)鏈條,找到控件與所屬控制器的關(guān)聯(lián)
- 需要自定義圖標(biāo),直接將圖片導(dǎo)入
App包即可- 添加的方法,方法名稱(chēng)加上自定義前綴,保證命名唯一
HOOK關(guān)鍵方法,完成界面與功能MSHookIvar:獲取對(duì)象下的成員變量












