iOS逆向?qū)崙?zhàn)--027:自動(dòng)搶紅包UI搭建

使用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)入WeChat

完善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ì)象下的成員變量
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容