iOS逆向实战--027:自动抢红包UI搭建

使用MokeyDevWeChat进行重签名并安装,在设置页,增加自动抢红包的UI,包含是否启用自动抢红包功能的开关,以及抢红包时的手速设置

界面分析

使用class-dump导出全部头文件

./class-dump -H WeChat -o ./header/

使用MokeyDev重签名wx8.0.2.ipa

真机运行项目,使用Debug Viwe找到设置页的控制器名称

使用Debug Viwe时,如果经常卡死,可以先将其暂停/继续一次

使用Cycript附加进程

使用pvcs()找到设置页的控制器

打印控制器View下的所有视图,从中找到UITableView,并找到对应的数据源

打开WCTableViewManager.h文件,找到数据源和关键方法

后续对关键方法进行HOOK

精准定位注入点

找到影响UITableView展示行数的数据源

WCTableViewManager中的numberOfSectionsInTableView方法进行HOOK,打印数组总数和Section

#import <UIKit/UIKit.h>

@interface WCTableViewManager : NSObject
@property(retain, nonatomic) NSMutableArray *sections;
@end

%hook WCTableViewManager

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
   NSLog(@"数据源:%ld,Sections:%ld", (long)self.sections.count, (long)[tableView numberOfSections]);
   return %orig;
}

%end

真机运行项目,查看HOOK之后的打印结果

从打印结果来看,UITableView的显示行数和数组总数是一致的。但也打印出其他页面的内容,证明WCTableViewManager在项目中是通用的。如果想对其HOOK,需要精准定位在设置页,不能影响其他功能

想要精准定位,需要在WCTableViewManager中,对所属控制器进行判断

我们要找到WCTableViewManager和控制器的关联

找到WCTableViewManager

#0x280bfe5e0
-------------------------
#"<WCTableViewManager: 0x280bfe5e0>"

找到UITableView,通过响应链条,向下找一层

#0x280bfe5e0.tableView.nextResponder
-------------------------
#"<UIView: 0x1157e38f0; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x281edca00>>"

通过响应链条,再向下找一层

#0x280bfe5e0.tableView.nextResponder.nextResponder
-------------------------
#"<NewSettingViewController: 0x116cb8400>"

通过响应链条,成功找到NewSettingViewController

修改代码,增加判断条件,保证HOOK代码仅在设置页有效

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
   
   if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)]){
       NSLog(@"数据源:%ld,Sections:%ld", (long)self.sections.count, (long)[tableView numberOfSections]);
   }

   return %orig;
}

真机运行项目,查看HOOK之后的打印结果

仅在NewSettingViewController中打印结果

修改界面

确保代码仅在NewSettingViewController中生效,接下来对几个关键方法进行HOOK,将界面修改成我们预期的样子

增加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;
}

为了编译通过,需要在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,只设置背景色,看一下运行后的结果,确认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;
}

真机运行项目,查看UI效果

修改NewSettingViewController成功,在原有界面的下方,增加了自定义Cell

完善界面

确认HOOK代码是有效的,下面完善自定义Cell的界面

将自定义图标,导入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 = @"自动抢红包";
           
           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 = @"等待时间(秒)";
           
           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 ? @"启用" : @"禁用"));
}

真机运行项目,查看UI效果

实现功能

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 ? @"hook_auto_en" : @"hook_auto_dis")];
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:@"hook_wait"];
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;

真机运行项目,查看UI效果

优化

整体的界面和功能都已经完成,还有两个小问题需要优化

  • 触发文本框,键盘弹出,会遮挡底部的功能区域
  • 设置页的列表滑动时,键盘无法自动收起,影响体验

解决遮挡问题

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效果

总结

自动抢红包UI搭建

  • 使用class-dump,导出目标App的头文件
  • 使用MokeyDev重签名并运行App
  • 使用Debug Viwe,快速定位目标控制器
  • 使用Cycript,分析控制器中的视图、对象、数据源
  • 在对应的头文件中,找到关键的方法和属性
  • 需要精准定位到注入点,不能影响其他功能
  • 可以使用响应链条,找到控件与所属控制器的关联
  • 需要自定义图标,直接将图片导入App包即可
  • 添加的方法,方法名称加上自定义前缀,保证命名唯一
  • HOOK关键方法,完成界面与功能
  • MSHookIvar:获取对象下的成员变量
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容